diff --git a/docs/resources/saml2_identity_provider.md b/docs/resources/saml2_identity_provider.md index 4e914df..fe01d7d 100644 --- a/docs/resources/saml2_identity_provider.md +++ b/docs/resources/saml2_identity_provider.md @@ -49,3 +49,7 @@ resource "splitsecure_saml2_identity_provider" "aws_console" { - `id` (String) Resource s2r URI of the identity provider. - `metadata_xml` (String) SAML IdP metadata document built from the IdP's signing certificate, provider_id, and SSO URLs. Suitable for aws_iam_saml_provider. +- `signing_certificate_der` (String) Base64-encoded DER X.509 signing certificate -- the same bytes as the contents of `signing_certificate_pem` between its BEGIN/END markers. Use this when a downstream consumer wants the unwrapped certificate body (e.g. the `` element of an SP metadata document). +- `signing_certificate_pem` (String) PEM-encoded X.509 signing certificate the IdP attaches to assertions. Suitable for SPs that take the raw certificate (e.g. tls_certificate-style consumers, custom SAML stacks). +- `signing_public_key_der` (String) Base64-encoded SubjectPublicKeyInfo DER -- the bytes between the BEGIN/END markers of `signing_public_key_pem`. +- `signing_public_key_pem` (String) PEM-encoded SubjectPublicKeyInfo extracted from the signing certificate. Suitable for SPs that pin a bare public key rather than the wrapping certificate. diff --git a/splitsecure/services/saml2/identity_provider.go b/splitsecure/services/saml2/identity_provider.go index 1d13362..9f33ffb 100644 --- a/splitsecure/services/saml2/identity_provider.go +++ b/splitsecure/services/saml2/identity_provider.go @@ -2,6 +2,9 @@ package saml2 import ( "context" + "crypto/x509" + "encoding/base64" + "encoding/pem" "fmt" "strings" @@ -33,16 +36,20 @@ type saml2IdentityProvider struct { } type saml2IdentityProviderModel struct { - ID types.String `tfsdk:"id"` - TeamS2R types.String `tfsdk:"team_s2r"` - ProviderID types.String `tfsdk:"provider_id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - NotificationPolicy types.String `tfsdk:"notification_policy"` - SSOURL types.String `tfsdk:"sso_url"` - SSOURLPost types.String `tfsdk:"sso_url_post"` - Justification types.String `tfsdk:"justification"` - MetadataXML types.String `tfsdk:"metadata_xml"` + ID types.String `tfsdk:"id"` + TeamS2R types.String `tfsdk:"team_s2r"` + ProviderID types.String `tfsdk:"provider_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + NotificationPolicy types.String `tfsdk:"notification_policy"` + SSOURL types.String `tfsdk:"sso_url"` + SSOURLPost types.String `tfsdk:"sso_url_post"` + Justification types.String `tfsdk:"justification"` + MetadataXML types.String `tfsdk:"metadata_xml"` + SigningCertificatePEM types.String `tfsdk:"signing_certificate_pem"` + SigningCertificateDER types.String `tfsdk:"signing_certificate_der"` + SigningPublicKeyPEM types.String `tfsdk:"signing_public_key_pem"` + SigningPublicKeyDER types.String `tfsdk:"signing_public_key_der"` } // NewIdentityProvider returns a factory for the SAML2 IdP resource. @@ -154,6 +161,24 @@ func (r *saml2IdentityProvider) Schema(_ context.Context, _ resource.SchemaReque Computed: true, Description: "SAML IdP metadata document built from the IdP's signing certificate, provider_id, and SSO URLs. Suitable for aws_iam_saml_provider.", }, + "signing_certificate_pem": schema.StringAttribute{ + Computed: true, + Description: "PEM-encoded X.509 signing certificate the IdP attaches to assertions. Suitable for SPs that take the raw certificate (e.g. tls_certificate-style consumers, custom SAML stacks).", + }, + "signing_certificate_der": schema.StringAttribute{ + Computed: true, + Description: "Base64-encoded DER X.509 signing certificate -- the same bytes as the contents of `signing_certificate_pem` between its " + + "BEGIN/END markers. Use this when a downstream consumer wants the unwrapped certificate body " + + "(e.g. the `` element of an SP metadata document).", + }, + "signing_public_key_pem": schema.StringAttribute{ + Computed: true, + Description: "PEM-encoded SubjectPublicKeyInfo extracted from the signing certificate. Suitable for SPs that pin a bare public key rather than the wrapping certificate.", + }, + "signing_public_key_der": schema.StringAttribute{ + Computed: true, + Description: "Base64-encoded SubjectPublicKeyInfo DER -- the bytes between the BEGIN/END markers of `signing_public_key_pem`.", + }, }, } } @@ -342,5 +367,29 @@ func populateIDPModel(m *saml2IdentityProviderModel, resourceS2R string, rec *co } m.MetadataXML = types.StringValue(string(xmlBytes)) + der := idp.GetX509Certificate() + m.SigningCertificateDER = types.StringValue(base64.StdEncoding.EncodeToString(der)) + m.SigningCertificatePEM = types.StringValue(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))) + + // Cert parse + SPKI marshal cannot realistically fail here -- idpMetadataXML + // already required a parseable cert -- but surface errors explicitly rather + // than silently producing empty public-key attributes. + cert, err := x509.ParseCertificate(der) + if err != nil { + var d diag.Diagnostics + d.AddError("Parsing SAML2 IdP signing certificate", err.Error()) + + return d + } + spkiDER, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + var d diag.Diagnostics + d.AddError("Marshaling SAML2 IdP signing public key", err.Error()) + + return d + } + m.SigningPublicKeyDER = types.StringValue(base64.StdEncoding.EncodeToString(spkiDER)) + m.SigningPublicKeyPEM = types.StringValue(string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: spkiDER}))) + return nil } diff --git a/splitsecure/services/saml2/schema_test.go b/splitsecure/services/saml2/schema_test.go index 9946c38..23bb22a 100644 --- a/splitsecure/services/saml2/schema_test.go +++ b/splitsecure/services/saml2/schema_test.go @@ -45,7 +45,7 @@ func TestIDPSchema_ComputedAttributes(t *testing.T) { s := schemaForResource(t, &saml2IdentityProvider{}) - for _, name := range []string{"id", "metadata_xml"} { + for _, name := range []string{"id", "metadata_xml", "signing_certificate_pem", "signing_certificate_der", "signing_public_key_pem", "signing_public_key_der"} { attr, ok := s.Attributes[name] if !ok { t.Fatalf("computed attribute %q missing from IdP schema", name)