logs-analyzer/signoz/ee/query-service/sso/saml/request.go

108 lines
3.7 KiB
Go
Raw Permalink Normal View History

2024-09-02 22:47:30 +03:00
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
}