mirror of
https://github.com/hanzo-fi/auth.git
synced 2026-10-30 20:33:21 +00:00
262 lines
8.6 KiB
Go
262 lines
8.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
authlib "github.com/hanzo-fi/go-libs/v3/auth"
|
|
oidclib "github.com/hanzo-fi/go-libs/v3/oidc"
|
|
"github.com/go-jose/go-jose/v4"
|
|
"net/http"
|
|
"os"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/hanzo-fi/go-libs/v3/aws/iam"
|
|
"github.com/hanzo-fi/go-libs/v3/bun/bunconnect"
|
|
"github.com/hanzo-fi/go-libs/v3/collectionutils"
|
|
"github.com/hanzo-fi/go-libs/v3/licence"
|
|
"github.com/hanzo-fi/go-libs/v3/logging"
|
|
|
|
"github.com/hanzo-fi/go-libs/v3/otlp"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
|
|
auth "github.com/hanzo-fi/auth/pkg"
|
|
"github.com/hanzo-fi/auth/pkg/api"
|
|
"github.com/hanzo-fi/auth/pkg/delegatedauth"
|
|
"github.com/hanzo-fi/auth/pkg/oidc"
|
|
"github.com/hanzo-fi/auth/pkg/storage/sqlstorage"
|
|
sharedapi "github.com/hanzo-fi/go-libs/v3/api"
|
|
"github.com/hanzo-fi/go-libs/v3/otlp/otlpmetrics"
|
|
"github.com/hanzo-fi/go-libs/v3/otlp/otlptraces"
|
|
"github.com/hanzo-fi/go-libs/v3/service"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/fx/messagingfx"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/messaging/publish"
|
|
logging5 "github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
zLogging "github.com/zitadel/logging"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
const (
|
|
ServiceName = "auth"
|
|
|
|
DelegatedClientIDFlag = "delegated-client-id"
|
|
DelegatedClientSecretFlag = "delegated-client-secret"
|
|
DelegatedIssuerFlag = "delegated-issuer"
|
|
BaseUrlFlag = "base-url"
|
|
AuthIssuersFlag = "auth-issuers"
|
|
ListenFlag = "listen"
|
|
SigningKeyFlag = "signing-key"
|
|
ConfigFlag = "config"
|
|
|
|
defaultSigningKey = `
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEpQIBAAKCAQEAth3atoCXldJgHH9EWnZQMvw5O+vVNKMcvrllEGQsLxvIA5xy
|
|
YPnFt2xU7k1dcN5ViBqPiigVHZNeyyHcdVclg26zqjEwYUqH+OPiRFeBn0SwOG+d
|
|
ZLpOIJdKt7OjmUG0xN9egq81dbPVPBPckuWqB9XMWmM+dtqydBX4lekj+Q1hFn5E
|
|
WXXuAs9aLIc8DzPz8B+oqwLKZ6k6kC5vpj+EaBt8ExVywrWftkWewGWRO7fLw0Fj
|
|
7hamaA1ZTYEqCN+MLDLEd6qmtC2cdgVhZM0RG2OnTiq5lGzNFmLXGOsquc35HSQj
|
|
OQqcLL+e/72K3giJ1YCqYWAIIJcc/kNKU8HtpwIDAQABAoIBAFY+dSEQbLjq09Er
|
|
A/fDJ9+9Sm1yFZnD1Q0NRysoBTSZ93KeWBxMrLFcgCwKP0IASIkX6voGWVmUPMP9
|
|
2SVIi99eQX9LpBmu7g2T/cdXmW8PXFSdpu/Yur78ZsnwLH2bfDvvfBZvWuXOsCCv
|
|
VznJwWfMe+YiMaafkvsenIaBziNWwUeVGHCWl5f++KGGbWFZjhkRZyjKWfMYflig
|
|
EG5e+WaXagCjTah5pUkmvLj3jmB1iGA/Askm8S5QyTt6Z+SIEk+i5T3qCiLFNvzp
|
|
7OeSyfbmWWzBYTiSvEoHhaHfdeicUyOpRthc33bb7LnfIWDG3Z+WE0o6U1nR8o7U
|
|
t5dsj2ECgYEA7SEuBpd/3wdNVLQSI/RHKKO3sdlymh7yRFf7OAn/UxnSJbSNx4y4
|
|
GAEdJD9KwSQlyekLITF+xc0IuyFHOmvuzp1+/LxK/QTY4dcdlwl/r1kmwBbTeR0e
|
|
yl9RtulHXmP+Ss/PZgwR081Lk7zlRkh1busyAOmCE4mJW/IvNBze0dsCgYEAxJvy
|
|
PcbaLVk497U9cUGznsSbbsyq7JGLkBgTu3eQ/yRgoE7pvagF7dV1gQGuCYjOaYml
|
|
U4d95FLPoiE+CE0g2uyouFEsD1UhggTADP33BidUKUcF1ub9VVNcWs4I5LeWPY/X
|
|
5vcpOCAkmRZWT5rieAECdIsfRTnePVyn2L7amyUCgYEAqsZAfWLSJm791Eiy383n
|
|
CW+OtbjiffhXhbzPIbaheNmZrKnxiYrgcfkrYZVrYtmDlXwOFeOtZwqYhRwcTgi5
|
|
PXfTonSAlOPOxibEGqgumrvb2m8V8Z11NU2cbdxnF6Vv17T9qoJ6vEyXZ1iczhcU
|
|
68LaiimhEiz1DZDHSgKYvg0CgYEAjVZyQXjXVWxjqKdQ4T9TKhq6hl95rJFA3DiC
|
|
zuy4fsKe9/9ixyWoBX7DdxdHDrGbeYErKa4okV/6xdnR51PS/67L55zq6KbRbM+P
|
|
ZIeZ8oGJXhchmoj5q0I/DUQ6Xnmf9ueWVQJvTlrFFIxbReTZU12ebzuoIjLkkgYu
|
|
34DsVEUCgYEAtHm/aO7/2UJT40PMO+VDvBCEixPtt6j72fLaW8btgVRAnhp9qaWX
|
|
Cv6TRZPe2y6Bbgg4Q3FuF0DMqx6ongFKQAWo3DkqNFCGRgjJMQ9JbcfOnGySq4U+
|
|
EL/wy5C80pa3jahniqVgO5L6zz0ZLtRIRE7aCtCIu826gctJ1+ShIso=
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
)
|
|
|
|
func otlpHttpClientModule(debug bool) fx.Option {
|
|
return fx.Decorate(func() *http.Client {
|
|
return otlpHttpClient(debug)
|
|
})
|
|
}
|
|
|
|
func otlpHttpClient(debug bool) *http.Client {
|
|
return &http.Client{
|
|
Transport: otlp.NewRoundTripper(http.DefaultTransport, debug, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
|
|
str := fmt.Sprintf("%s %s", r.Method, r.URL.Path)
|
|
if len(r.URL.Query()) == 0 {
|
|
return str
|
|
}
|
|
|
|
return fmt.Sprintf("%s?%s", str, r.URL.Query().Encode())
|
|
})),
|
|
}
|
|
}
|
|
|
|
func newServeCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "serve",
|
|
RunE: runServe,
|
|
}
|
|
|
|
cmd.Flags().String(DelegatedIssuerFlag, "", "Delegated OIDC issuer")
|
|
cmd.Flags().String(DelegatedClientIDFlag, "", "Delegated OIDC client id")
|
|
cmd.Flags().String(DelegatedClientSecretFlag, "", "Delegated OIDC client secret")
|
|
cmd.Flags().String(BaseUrlFlag, "http://localhost:8080", "Base service url")
|
|
cmd.Flags().StringSlice(AuthIssuersFlag, []string{}, "Additional trusted issuer URLs for multi-domain support")
|
|
cmd.Flags().String(SigningKeyFlag, defaultSigningKey, "Signing key")
|
|
cmd.Flags().String(ListenFlag, ":8080", "Listening address")
|
|
cmd.Flags().String(ConfigFlag, "", "Config file name without extension")
|
|
cmd.Flags().Bool(authlib.AuthCheckScopesFlag, false, "Enable scope checking")
|
|
|
|
publish.AddFlags("auth", cmd.Flags())
|
|
service.AddFlags(cmd.Flags())
|
|
licence.AddFlags(cmd.Flags())
|
|
otlp.AddFlags(cmd.Flags())
|
|
otlptraces.AddFlags(cmd.Flags())
|
|
otlpmetrics.AddFlags(cmd.Flags())
|
|
bunconnect.AddFlags(cmd.Flags())
|
|
iam.AddFlags(cmd.Flags())
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runServe(cmd *cobra.Command, _ []string) error {
|
|
baseUrl, _ := cmd.Flags().GetString(BaseUrlFlag)
|
|
if baseUrl == "" {
|
|
return errors.New("base url must be defined")
|
|
}
|
|
|
|
additionalIssuers, _ := cmd.Flags().GetStringSlice(AuthIssuersFlag)
|
|
trustedIssuers := append([]string{baseUrl}, additionalIssuers...)
|
|
|
|
signingKey, _ := cmd.Flags().GetString(SigningKeyFlag)
|
|
if signingKey == "" {
|
|
return errors.New("signing key must be defined")
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(signingKey))
|
|
if block == nil {
|
|
return errors.New("invalid signing key, cannot parse as PEM")
|
|
}
|
|
|
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type configuration struct {
|
|
Clients []auth.StaticClient `json:"clients" yaml:"clients"`
|
|
}
|
|
o := configuration{}
|
|
|
|
config, _ := cmd.Flags().GetString(ConfigFlag)
|
|
if config != "" {
|
|
configFile, err := os.Open(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := yaml.NewDecoder(configFile).Decode(&o); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
o.Clients = collectionutils.Map(o.Clients, func(client auth.StaticClient) auth.StaticClient {
|
|
c, err := client.FromEnvironment()
|
|
if err != nil {
|
|
logging.FromContext(cmd.Context()).Errorf("error while loading secrets from environment: %v", err)
|
|
}
|
|
return c
|
|
})
|
|
|
|
zLogging.SetOutput(cmd.OutOrStdout())
|
|
|
|
connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
listen, _ := cmd.Flags().GetString(ListenFlag)
|
|
checkScopes, _ := cmd.Flags().GetBool(authlib.AuthCheckScopesFlag)
|
|
options := []fx.Option{
|
|
otlpHttpClientModule(service.IsDebug(cmd)),
|
|
fx.Provide(func() logging5.Logger {
|
|
return logging5.NewDefaultLogger(cmd.OutOrStdout(), service.IsDebug(cmd), false, false)
|
|
}),
|
|
messagingfx.PublishModuleFromFlags(cmd, service.IsDebug(cmd)),
|
|
fx.Supply(fx.Annotate(cmd.Context(), fx.As(new(context.Context)))),
|
|
sqlstorage.Module(*connectionOptions, key, service.IsDebug(cmd), o.Clients...),
|
|
oidc.Module(key, baseUrl, trustedIssuers, o.Clients...),
|
|
api.Module(
|
|
listen,
|
|
baseUrl,
|
|
trustedIssuers,
|
|
sharedapi.ServiceInfo{
|
|
Version: Version,
|
|
Debug: service.IsDebug(cmd),
|
|
},
|
|
service.IsDebug(cmd),
|
|
),
|
|
authlib.Module(authlib.ModuleConfig{
|
|
Enabled: true,
|
|
Issuers: trustedIssuers,
|
|
CheckScopes: checkScopes,
|
|
Service: ServiceName,
|
|
}),
|
|
fx.Decorate(func() authlib.Authenticator {
|
|
staticKeySet := oidclib.NewStaticKeySet(jose.JSONWebKey{
|
|
Key: &key.PublicKey,
|
|
KeyID: oidc.KeyID,
|
|
Algorithm: string(jose.RS256),
|
|
Use: oidclib.KeyUseSignature,
|
|
})
|
|
keySets := make(map[string]oidclib.KeySet, len(trustedIssuers))
|
|
for _, issuer := range trustedIssuers {
|
|
keySets[issuer] = staticKeySet
|
|
}
|
|
return authlib.NewJWTAuth(keySets, ServiceName, checkScopes)
|
|
}),
|
|
}
|
|
|
|
delegatedIssuer, _ := cmd.Flags().GetString(DelegatedIssuerFlag)
|
|
if delegatedIssuer != "" {
|
|
delegatedClientID, _ := cmd.Flags().GetString(DelegatedClientIDFlag)
|
|
if delegatedClientID == "" {
|
|
return errors.New("delegated client id must be defined")
|
|
}
|
|
|
|
delegatedClientSecret, _ := cmd.Flags().GetString(DelegatedClientSecretFlag)
|
|
if delegatedClientSecret == "" {
|
|
return errors.New("delegated client secret must be defined")
|
|
}
|
|
|
|
options = append(options,
|
|
fx.Supply(delegatedauth.Config{
|
|
Issuer: delegatedIssuer,
|
|
ClientID: delegatedClientID,
|
|
ClientSecret: delegatedClientSecret,
|
|
RedirectURL: fmt.Sprintf("%s/authorize/callback", baseUrl),
|
|
}),
|
|
delegatedauth.Module(),
|
|
)
|
|
}
|
|
|
|
options = append(options,
|
|
otlp.FXModuleFromFlags(cmd, otlp.WithServiceVersion(Version)),
|
|
otlptraces.FXModuleFromFlags(cmd),
|
|
otlpmetrics.FXModuleFromFlags(cmd),
|
|
licence.FXModuleFromFlags(cmd, ServiceName),
|
|
)
|
|
|
|
return service.New(cmd.OutOrStdout(), options...).Run(cmd)
|
|
}
|