108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
package saml
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"strings"
|
|
|
|
saml2 "github.com/russellhaering/gosaml2"
|
|
dsig "github.com/russellhaering/goxmldsig"
|
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func LoadCertificateStore(certString string) (dsig.X509CertificateStore, error) {
|
|
certStore := &dsig.MemoryX509CertificateStore{
|
|
Roots: []*x509.Certificate{},
|
|
}
|
|
|
|
certData, err := base64.StdEncoding.DecodeString(certString)
|
|
if err != nil {
|
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to read certificate: %v", err))
|
|
}
|
|
|
|
idpCert, err := x509.ParseCertificate(certData)
|
|
if err != nil {
|
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to prepare saml request, invalid cert: %s", err.Error()))
|
|
}
|
|
|
|
certStore.Roots = append(certStore.Roots, idpCert)
|
|
|
|
return certStore, nil
|
|
}
|
|
|
|
func LoadCertFromPem(certString string) (dsig.X509CertificateStore, error) {
|
|
certStore := &dsig.MemoryX509CertificateStore{
|
|
Roots: []*x509.Certificate{},
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(certString))
|
|
if block == nil {
|
|
return certStore, fmt.Errorf("no valid pem cert found")
|
|
}
|
|
|
|
idpCert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return certStore, fmt.Errorf(fmt.Sprintf("failed to parse pem cert: %s", err.Error()))
|
|
}
|
|
|
|
certStore.Roots = append(certStore.Roots, idpCert)
|
|
|
|
return certStore, nil
|
|
}
|
|
|
|
// PrepareRequest prepares authorization URL (Idp Provider URL)
|
|
func PrepareRequest(issuer, acsUrl, audience, entity, idp, certString string) (*saml2.SAMLServiceProvider, error) {
|
|
var certStore dsig.X509CertificateStore
|
|
if certString == "" {
|
|
return nil, fmt.Errorf("invalid certificate data")
|
|
}
|
|
|
|
var err error
|
|
if strings.Contains(certString, "-----BEGIN CERTIFICATE-----") {
|
|
certStore, err = LoadCertFromPem(certString)
|
|
} else {
|
|
certStore, err = LoadCertificateStore(certString)
|
|
}
|
|
// certificate store can not be created, throw error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
randomKeyStore := dsig.RandomKeyStoreForTest()
|
|
|
|
// SIGNOZ_SAML_RETURN_URL env var would support overriding window.location
|
|
// as return destination after saml request is complete from IdP side.
|
|
// this var is also useful for development, as it is easy to override with backend endpoint
|
|
// e.g. http://localhost:8080/api/v1/complete/saml
|
|
acsUrl = constants.GetOrDefaultEnv("SIGNOZ_SAML_RETURN_URL", acsUrl)
|
|
|
|
sp := &saml2.SAMLServiceProvider{
|
|
IdentityProviderSSOURL: idp,
|
|
IdentityProviderIssuer: entity,
|
|
ServiceProviderIssuer: issuer,
|
|
AssertionConsumerServiceURL: acsUrl,
|
|
SignAuthnRequests: true,
|
|
AllowMissingAttributes: true,
|
|
|
|
// about cert stores -sender(signoz app) and receiver (idp)
|
|
// The random key (random key store) is sender cert. The public cert store(IDPCertificateStore) that you see on org domain is receiver cert (idp provided).
|
|
// At the moment, the library we use doesn't bother about sender cert and IdP too. It just adds additional layer of security, which we can explore in future versions
|
|
// The receiver (Idp) cert will be different for each org domain. Imagine cloud setup where each company setups their domain that integrates with their Idp.
|
|
// @signoz.io
|
|
// @next.io
|
|
// Each of above will have their own Idp setup and hence separate public cert to decrypt the response.
|
|
// The way SAML request travels is -
|
|
// SigNoz Backend -> IdP Login Screen -> SigNoz Backend -> SigNoz Frontend
|
|
// ---------------- | -------------------| -------------------------------------
|
|
// The dotted lines indicate request boundries. So if you notice, the response from Idp starts a new request. hence we need relay state to pass the context around.
|
|
|
|
IDPCertificateStore: certStore,
|
|
SPKeyStore: randomKeyStore,
|
|
}
|
|
zap.L().Debug("SAML request", zap.Any("sp", sp))
|
|
return sp, nil
|
|
}
|