diff --git a/lib/fixtures/fixtures.go b/lib/fixtures/fixtures.go index cf86dd07d1959..efec8967ff9e6 100644 --- a/lib/fixtures/fixtures.go +++ b/lib/fixtures/fixtures.go @@ -150,6 +150,35 @@ spec: C7yZIZM0DuazwkaenExrncvPtF6KL7eccudcknNjhRjFD3Yx1nNXgbVRHvVaElm0YxLiLcl8l0Rn pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwRurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` +const SAMLOktaConnectorV2WithPadding = `kind: saml +version: v2 +metadata: + name: OktaSAML + namespace: default +spec: + acs: https://localhost:3080/v1/webapi/saml/acs + attributes_to_roles: + - {name: "groups", value: "Everyone", roles: ["admin"]} + entity_descriptor: | + MIIDpDCCAoygAwIBAgIGAVvvlUB6MA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi04MTMzNTQxHDAaBgkqhkiG9w0BCQEW + DWluZm9Ab2t0YS5jb20wHhcNMTcwNTA5MjMzODQ3WhcNMjcwNTA5MjMzOTQ3WjCBkjELMAkGA1UE + BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV + BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtODEzMzU0MRwwGgYJ + KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA + ltQB+ZTGKoaNiWQRZ/bzl9oNmbjFyLiVlDASaYnuv1yBx70/Tzr9VXn0gWkl5yH0zIpzREvR5qM1 + VAaH3dgNbxTg15f0e5xDk7r5ggS11mX5p8S1Ca9UQmqhRRv7jhMJxHbCy4rFV5jO/uyNQDaMZLPd + zFuzpwKaWhy/UCQ3lDmNzxp3Q6T3FULV+fvs7tJp+8p6qfpoGkANGVfs/Jx/kgbbk0JZG2wk4VVl + b1rZTZJWQ6hCLwTAsD/WixcUx1BFTXmqoZTYNETATVJQ+bEMCVf8K4hxbH6hEgjoL//AE9zgpa1m + uvKwevYBvYZ/+VRy+It3d9mq73AdrG9vchE3qQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQASANAj + 8JQdBdKIrrbU6n1egwETwkOUwWyUja/5t+C/RIZPuKP5XmsUhFecbCrML2+M7HG0l5leqyD3u5pS + yhyBz99QWZegoRJy05tciuQUCyPrp6zDzl5De3byq5WQ6Ke+uiRb2GFdDNDhLuMlE48aLWyjm4qh + 31Q0/wAWJ1zwmrYxu4p/OhZemU7myuSF5tp35rzV3CPRN31d2UcZAwzMUgwKkCE3yT1o+lLskg/k + C7yZIZM0DuazwkaenExrncvPtF6KL7eccudcknNjhRjFD3Yx1nNXgbVRHvVaElm0YxLiLcl8l0Rn + pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwR + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` + const ( TLSCACertPEM = apifixtures.TLSCACertPEM TLSCAKeyPEM = apifixtures.TLSCAKeyPEM diff --git a/lib/services/saml.go b/lib/services/saml.go index 61a8651ad60b5..b0121584228f3 100644 --- a/lib/services/saml.go +++ b/lib/services/saml.go @@ -162,7 +162,13 @@ func CheckSAMLEntityDescriptor(entityDescriptor string) ([]*x509.Certificate, er for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors { for _, samlCert := range kd.KeyInfo.X509Data.X509Certificates { - certData, err := base64.StdEncoding.DecodeString(strings.TrimSpace(samlCert.Data)) + // The certificate is base64 encoded and can be split into multiple lines. + // Each line can be padded with spaces/tabs, so we need to remove them first + // before decoding otherwise we'll get an error. + // We need to run this through strings.Fields to remove spaces/tabs + // from each line and then join them back with newlines. + // The last step isn't strictly necessary, but it makes payload more readable. + certData, err := base64.StdEncoding.DecodeString(strings.Join(strings.Fields(samlCert.Data), "\n")) if err != nil { return nil, trace.Wrap(err, "failed to decode certificate defined in entity_descriptor") } diff --git a/lib/services/saml_test.go b/lib/services/saml_test.go index 3548b4f549e1f..b83bb3e22d38e 100644 --- a/lib/services/saml_test.go +++ b/lib/services/saml_test.go @@ -54,20 +54,25 @@ func TestParseFromMetadata(t *testing.T) { func TestCheckSAMLEntityDescriptor(t *testing.T) { t.Parallel() - input := fixtures.SAMLOktaConnectorV2 - - decoder := kyaml.NewYAMLOrJSONDecoder(strings.NewReader(input), defaults.LookaheadBufSize) - var raw UnknownResource - err := decoder.Decode(&raw) - require.NoError(t, err) + for name, input := range map[string]string{ + "without certificate padding": fixtures.SAMLOktaConnectorV2, + "with certificate padding": fixtures.SAMLOktaConnectorV2WithPadding, + } { + t.Run(name, func(t *testing.T) { + decoder := kyaml.NewYAMLOrJSONDecoder(strings.NewReader(input), defaults.LookaheadBufSize) + var raw UnknownResource + err := decoder.Decode(&raw) + require.NoError(t, err) - oc, err := UnmarshalSAMLConnector(raw.Raw) - require.NoError(t, err) + oc, err := UnmarshalSAMLConnector(raw.Raw) + require.NoError(t, err) - ed := oc.GetEntityDescriptor() - certs, err := CheckSAMLEntityDescriptor(ed) - require.NoError(t, err) - require.Len(t, certs, 1) + ed := oc.GetEntityDescriptor() + certs, err := CheckSAMLEntityDescriptor(ed) + require.NoError(t, err) + require.Len(t, certs, 1) + }) + } } func TestValidateRoles(t *testing.T) {