Rewrite identity and access management
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -17,8 +17,10 @@ package probod
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -27,6 +29,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pemutil "go.probo.inc/probo/pkg/crypto/pem"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
proxyproto "github.com/pires/go-proxyproto"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@@ -38,8 +42,6 @@ import (
|
||||
"go.gearno.de/kit/unit"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.probo.inc/probo/pkg/agents"
|
||||
"go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/awsconfig"
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
"go.probo.inc/probo/pkg/certmanager"
|
||||
@@ -48,14 +50,13 @@ import (
|
||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||
"go.probo.inc/probo/pkg/crypto/keys"
|
||||
"go.probo.inc/probo/pkg/crypto/passwdhash"
|
||||
"go.probo.inc/probo/pkg/crypto/pem"
|
||||
"go.probo.inc/probo/pkg/filemanager"
|
||||
"go.probo.inc/probo/pkg/html2pdf"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/mailer"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/saferedirect"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
"go.probo.inc/probo/pkg/server"
|
||||
"go.probo.inc/probo/pkg/server/api"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -123,6 +124,7 @@ func New() *Implm {
|
||||
},
|
||||
DisableSignup: false,
|
||||
InvitationConfirmationTokenValidity: 3600,
|
||||
PasswordResetTokenValidity: 3600,
|
||||
SAML: samlConfig{
|
||||
SessionDuration: 604800,
|
||||
CleanupIntervalSeconds: 86400,
|
||||
@@ -277,51 +279,60 @@ func (impl *Implm) Run(
|
||||
|
||||
agent := agents.NewAgent(l.Named("agent"), agentConfig)
|
||||
|
||||
authService, err := auth.NewService(
|
||||
ctx,
|
||||
pgClient,
|
||||
impl.cfg.EncryptionKey,
|
||||
hp,
|
||||
impl.cfg.Auth.Cookie.Secret,
|
||||
impl.cfg.BaseURL.String(),
|
||||
impl.cfg.Auth.DisableSignup,
|
||||
time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create auth service: %w", err)
|
||||
}
|
||||
|
||||
authzService, err := authz.NewService(
|
||||
ctx,
|
||||
pgClient,
|
||||
impl.cfg.BaseURL.String(),
|
||||
impl.cfg.Auth.Cookie.Secret,
|
||||
time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create authz service: %w", err)
|
||||
}
|
||||
|
||||
fileManagerService := filemanager.NewService(s3Client)
|
||||
|
||||
samlService, err := auth.NewSAMLService(
|
||||
var samlCert *x509.Certificate
|
||||
var samlKey *rsa.PrivateKey
|
||||
if impl.cfg.Auth.SAML.Certificate != "" && impl.cfg.Auth.SAML.PrivateKey != "" {
|
||||
// Decode certificate
|
||||
certBlock, _ := pem.Decode([]byte(impl.cfg.Auth.SAML.Certificate))
|
||||
if certBlock == nil {
|
||||
return fmt.Errorf("cannot decode SAML certificate PEM block")
|
||||
}
|
||||
var err error
|
||||
samlCert, err = x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse SAML certificate: %w", err)
|
||||
}
|
||||
|
||||
// Decode private key
|
||||
signer, err := pemutil.DecodePrivateKey([]byte(impl.cfg.Auth.SAML.PrivateKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode SAML private key: %w", err)
|
||||
}
|
||||
var ok bool
|
||||
samlKey, ok = signer.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("SAML private key is not an RSA key")
|
||||
}
|
||||
}
|
||||
|
||||
iamService, err := iam.NewService(
|
||||
ctx,
|
||||
pgClient,
|
||||
impl.cfg.EncryptionKey,
|
||||
impl.cfg.BaseURL.String(),
|
||||
impl.cfg.Auth.SAML.SessionDurationTime(),
|
||||
impl.cfg.Auth.Cookie.Name,
|
||||
impl.cfg.Auth.Cookie.Secret,
|
||||
impl.cfg.Auth.SAML.Certificate,
|
||||
impl.cfg.Auth.SAML.PrivateKey,
|
||||
l.Named("saml"),
|
||||
fileManagerService,
|
||||
hp,
|
||||
iam.Config{
|
||||
DisableSignup: impl.cfg.Auth.DisableSignup,
|
||||
InvitationTokenValidity: time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity) * time.Second,
|
||||
PasswordResetTokenValidity: time.Duration(impl.cfg.Auth.PasswordResetTokenValidity) * time.Second,
|
||||
SessionDuration: time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour,
|
||||
Bucket: impl.cfg.AWS.Bucket,
|
||||
TokenSecret: impl.cfg.Auth.Cookie.Secret,
|
||||
BaseURL: impl.cfg.BaseURL.String(),
|
||||
EncryptionKey: impl.cfg.EncryptionKey,
|
||||
Certificate: samlCert,
|
||||
PrivateKey: samlKey,
|
||||
Logger: l.Named("iam"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create SAML service: %w", err)
|
||||
return fmt.Errorf("cannot create iam service: %w", err)
|
||||
}
|
||||
|
||||
var accountKey crypto.Signer
|
||||
if impl.cfg.CustomDomains.ACME.AccountKey != "" {
|
||||
accountKey, err = pem.DecodePrivateKey([]byte(impl.cfg.CustomDomains.ACME.AccountKey))
|
||||
accountKey, err = pemutil.DecodePrivateKey([]byte(impl.cfg.CustomDomains.ACME.AccountKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode ACME account key: %w", err)
|
||||
}
|
||||
@@ -370,8 +381,6 @@ func (impl *Implm) Run(
|
||||
html2pdfConverter,
|
||||
acmeService,
|
||||
fileManagerService,
|
||||
authService,
|
||||
authzService,
|
||||
l.Named("probo"),
|
||||
slackService,
|
||||
)
|
||||
@@ -386,7 +395,8 @@ func (impl *Implm) Run(
|
||||
impl.cfg.BaseURL.String(),
|
||||
impl.cfg.EncryptionKey,
|
||||
impl.cfg.TrustAuth.TokenSecret,
|
||||
authService,
|
||||
impl.cfg.GetSlackSigningSecret(),
|
||||
iamService,
|
||||
html2pdfConverter,
|
||||
fileManagerService,
|
||||
l,
|
||||
@@ -403,40 +413,23 @@ func (impl *Implm) Run(
|
||||
AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins,
|
||||
ExtraHeaderFields: impl.cfg.Api.ExtraHeaderFields,
|
||||
Probo: proboService,
|
||||
Auth: authService,
|
||||
Authz: authzService,
|
||||
IAM: iamService,
|
||||
Trust: trustService,
|
||||
Slack: slackService,
|
||||
SAML: samlService,
|
||||
ConnectorRegistry: defaultConnectorRegistry,
|
||||
BaseURL: impl.cfg.BaseURL,
|
||||
Agent: agent,
|
||||
SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.BaseURL.Host()},
|
||||
CustomDomainCname: impl.cfg.CustomDomains.CnameTarget,
|
||||
FileManager: fileManagerService,
|
||||
PGClient: pgClient,
|
||||
Logger: l.Named("http.server"),
|
||||
ConsoleAuth: api.ConsoleAuthConfig{
|
||||
CookieName: impl.cfg.Auth.Cookie.Name,
|
||||
CookieDomain: impl.cfg.Auth.Cookie.Domain,
|
||||
SessionDuration: time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour,
|
||||
CookieSecret: impl.cfg.Auth.Cookie.Secret,
|
||||
CookieSecure: impl.cfg.Auth.Cookie.Secure,
|
||||
},
|
||||
TrustAuth: api.TrustAuthConfig{
|
||||
CookieName: impl.cfg.TrustAuth.CookieName,
|
||||
CookieDomain: impl.cfg.TrustAuth.CookieDomain,
|
||||
CookieDuration: time.Duration(impl.cfg.TrustAuth.CookieDuration) * time.Hour,
|
||||
TokenDuration: time.Duration(impl.cfg.TrustAuth.TokenDuration) * time.Hour,
|
||||
ReportURLDuration: time.Duration(impl.cfg.TrustAuth.ReportURLDuration) * time.Minute,
|
||||
TokenSecret: impl.cfg.TrustAuth.TokenSecret,
|
||||
Scope: impl.cfg.TrustAuth.Scope,
|
||||
TokenType: impl.cfg.TrustAuth.TokenType,
|
||||
CookieSecure: impl.cfg.Auth.Cookie.Secure,
|
||||
},
|
||||
MCPConfig: api.MCPConfig{
|
||||
Version: "0.0.1",
|
||||
RequestTimeout: 30 * time.Second,
|
||||
MaxRequestSize: 10 * 1024 * 1024, // 10MB
|
||||
Cookie: securecookie.Config{
|
||||
Name: impl.cfg.Auth.Cookie.Name,
|
||||
Domain: impl.cfg.Auth.Cookie.Domain,
|
||||
Path: "/",
|
||||
MaxAge: int(time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour),
|
||||
Secret: impl.cfg.Auth.Cookie.Secret,
|
||||
Secure: impl.cfg.Auth.Cookie.Secure,
|
||||
HTTPOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -455,16 +448,20 @@ func (impl *Implm) Run(
|
||||
)
|
||||
|
||||
mailerCtx, stopMailer := context.WithCancel(context.Background())
|
||||
mailer := mailer.NewMailer(pgClient, l, mailer.Config{
|
||||
SenderEmail: impl.cfg.Notifications.Mailer.SenderEmail,
|
||||
SenderName: impl.cfg.Notifications.Mailer.SenderName,
|
||||
Addr: impl.cfg.Notifications.Mailer.SMTP.Addr,
|
||||
User: impl.cfg.Notifications.Mailer.SMTP.User,
|
||||
Password: impl.cfg.Notifications.Mailer.SMTP.Password,
|
||||
TLSRequired: impl.cfg.Notifications.Mailer.SMTP.TLSRequired,
|
||||
Timeout: time.Second * 10,
|
||||
Interval: time.Duration(impl.cfg.Notifications.Mailer.MailerInterval) * time.Second,
|
||||
})
|
||||
mailer := mailer.NewMailer(
|
||||
pgClient,
|
||||
l,
|
||||
mailer.Config{
|
||||
SenderEmail: impl.cfg.Notifications.Mailer.SenderEmail,
|
||||
SenderName: impl.cfg.Notifications.Mailer.SenderName,
|
||||
Addr: impl.cfg.Notifications.Mailer.SMTP.Addr,
|
||||
User: impl.cfg.Notifications.Mailer.SMTP.User,
|
||||
Password: impl.cfg.Notifications.Mailer.SMTP.Password,
|
||||
TLSRequired: impl.cfg.Notifications.Mailer.SMTP.TLSRequired,
|
||||
Timeout: time.Second * 10,
|
||||
Interval: time.Duration(impl.cfg.Notifications.Mailer.MailerInterval) * time.Second,
|
||||
},
|
||||
)
|
||||
wg.Go(
|
||||
func() {
|
||||
if err := mailer.Run(mailerCtx); err != nil {
|
||||
@@ -494,16 +491,13 @@ func (impl *Implm) Run(
|
||||
},
|
||||
)
|
||||
|
||||
samlCleanerCtx, stopSAMLCleaner := context.WithCancel(context.Background())
|
||||
samlCleaner := auth.NewCleaner(
|
||||
pgClient,
|
||||
impl.cfg.Auth.SAML.CleanupInterval(),
|
||||
l.Named("saml-cleaner"),
|
||||
)
|
||||
samlServiceCtx, stopSAMLService := context.WithCancel(context.Background())
|
||||
wg.Go(
|
||||
func() {
|
||||
if err := samlCleaner.Run(samlCleanerCtx); err != nil {
|
||||
cancel(fmt.Errorf("saml cleaner crashed: %w", err))
|
||||
if iamService.SAMLService != nil {
|
||||
if err := iamService.SAMLService.Run(samlServiceCtx); err != nil {
|
||||
cancel(fmt.Errorf("saml service crashed: %w", err))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -523,7 +517,7 @@ func (impl *Implm) Run(
|
||||
stopMailer()
|
||||
stopSlackSender()
|
||||
stopExportJobExporter()
|
||||
stopSAMLCleaner()
|
||||
stopSAMLService()
|
||||
stopApiServer()
|
||||
stopTrustCenterServer()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user