diff --git a/cmd/a3s/main.go b/cmd/a3s/main.go index da00259..00a2189 100644 --- a/cmd/a3s/main.go +++ b/cmd/a3s/main.go @@ -80,7 +80,7 @@ func main() { } } - m := bootstrap.MakeMongoManipulator(cfg.MongoConf, &hasher.Hasher{}) + m := bootstrap.MakeMongoManipulator(cfg.MongoConf, &hasher.Hasher{}, api.Manager()) if err := indexes.Ensure(m, api.Manager(), "a3s"); err != nil { zap.L().Fatal("Unable to ensure indexes", zap.Error(err)) } @@ -376,7 +376,7 @@ func main() { func createMongoDBAccount(cfg conf.MongoConf, username string) error { - m := bootstrap.MakeMongoManipulator(cfg, &hasher.Hasher{}) + m := bootstrap.MakeMongoManipulator(cfg, &hasher.Hasher{}, api.Manager()) db, closeFunc, _ := manipmongo.GetDatabase(m) defer closeFunc() diff --git a/go.mod b/go.mod index 2549ebb..91b1bdf 100644 --- a/go.mod +++ b/go.mod @@ -128,5 +128,3 @@ require ( k8s.io/helm v2.17.0+incompatible // indirect rsc.io/qr v0.2.0 // indirect ) - -replace github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.4.3 diff --git a/go.sum b/go.sum index 31268a8..10b162a 100644 --- a/go.sum +++ b/go.sum @@ -752,8 +752,10 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/pkgs/bootstrap/auth.go b/pkgs/bootstrap/auth.go new file mode 100644 index 0000000..13cff4d --- /dev/null +++ b/pkgs/bootstrap/auth.go @@ -0,0 +1,85 @@ +package bootstrap + +import ( + "crypto/x509" + + "go.aporeto.io/a3s/pkgs/token" + "go.aporeto.io/bahamut" + "go.aporeto.io/bahamut/authorizer/mtls" + "go.aporeto.io/bahamut/authorizer/simple" + "go.aporeto.io/elemental" +) + +// MakeCNExcluderFunc returns an mtls.VerifierFunc that rejects mtls verification +// if the certificate has the given Common Name. +func MakeCNExcluderFunc(cn string) mtls.VerifierFunc { + return func(cert *x509.Certificate) bool { return cert.Subject.CommonName != cn } +} + +// MakeMTLSRequestAuthenticator returns a bahamut.RequestAuthenticator that +// will allow requests presenting a client certificate signed by a CA present +// in the given caPool unless the given mtls.VerifierFunc disagrees. +func MakeMTLSRequestAuthenticator(caPool *x509.CertPool, verifier mtls.VerifierFunc) bahamut.RequestAuthenticator { + return mtls.NewMTLSRequestAuthenticator(defaultOption(caPool, verifier)) +} + +// MakeMTLSAuthorizer returns a bahamut.Authorizer that will allow requests +// presenting a client certificate signed by a CA present in the given caPool +// unless the given mtls.VerifierFunc disagrees. +func MakeMTLSAuthorizer(caPool *x509.CertPool, verifier mtls.VerifierFunc, ignoredIdentities []elemental.Identity) bahamut.Authorizer { + v, d, mf, mc := defaultOption(caPool, verifier) + return mtls.NewMTLSAuthorizer(v, d, ignoredIdentities, mf, mc) +} + +// MakeRequestAuthenticatorBypasser returns a bahamut.RequestAuthenticator that returns bahamut.ActionOK +// if the request is for one of the given identities. Otherwise it returns bahamut.Continue. +func MakeRequestAuthenticatorBypasser(identities []elemental.Identity) bahamut.RequestAuthenticator { + return simple.NewAuthenticator(makeBypasserFunc(identities), nil) +} + +// MakeSessionAuthenticatorBypasser returns a bahamut.SessionAuthenticator that returns bahamut.ActionOK +// if the request is for one of the given identities. Otherwise it returns bahamut.Continue. +func MakeSessionAuthenticatorBypasser(identities []elemental.Identity) bahamut.RequestAuthenticator { + return simple.NewAuthenticator(makeBypasserFunc(identities), nil) +} + +// MakeAuthorizerBypasser returns a bahamut.Authorizer that returns bahamut.ActionOK +// if the request is on one of the given identities. Otherwise it returns bahamut.Continue. +func MakeAuthorizerBypasser(identities []elemental.Identity) bahamut.Authorizer { + return simple.NewAuthorizer(makeBypasserFunc(identities)) +} + +func makeBypasserFunc(identities []elemental.Identity) func(ctx bahamut.Context) (bahamut.AuthAction, error) { + + return func(ctx bahamut.Context) (bahamut.AuthAction, error) { + for _, i := range identities { + if ctx.Request().Identity.IsEqual(i) { + return bahamut.AuthActionOK, nil + } + } + return bahamut.AuthActionContinue, nil + } +} + +func defaultOption(caPool *x509.CertPool, verifier mtls.VerifierFunc) (x509.VerifyOptions, mtls.DeciderFunc, mtls.VerifierFunc, mtls.CertificateCheckMode) { + + return x509.VerifyOptions{ + Roots: caPool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, + func(a bahamut.AuthAction, c bahamut.Context, s bahamut.Session) bahamut.AuthAction { + switch a { + case bahamut.AuthActionKO: + return bahamut.AuthActionContinue + case bahamut.AuthActionOK: + if token.FromRequest(c.Request()) != "" { + return bahamut.AuthActionContinue + } + return bahamut.AuthActionOK + default: + panic("should not reach here") + } + }, + verifier, + mtls.CertificateCheckModeHeaderOnly +} diff --git a/pkgs/bootstrap/clients.go b/pkgs/bootstrap/clients.go index 65f1d07..4d68115 100644 --- a/pkgs/bootstrap/clients.go +++ b/pkgs/bootstrap/clients.go @@ -6,10 +6,13 @@ import ( "fmt" "time" - "go.aporeto.io/a3s/pkgs/api" + "go.aporeto.io/a3s/pkgs/authenticator" "go.aporeto.io/a3s/pkgs/authlib" + "go.aporeto.io/a3s/pkgs/authorizer" "go.aporeto.io/a3s/pkgs/conf" + "go.aporeto.io/a3s/pkgs/permissions" "go.aporeto.io/a3s/pkgs/sharder" + "go.aporeto.io/a3s/pkgs/token" "go.aporeto.io/bahamut" "go.aporeto.io/elemental" "go.aporeto.io/manipulate" @@ -56,7 +59,7 @@ func MakeNATSClient(cfg conf.NATSConf) bahamut.PubSubClient { // MakeMongoManipulator returns a configured mongo manipulator. // This function is not meant to be used outside of the platform. It will fatal // anytime it feels like it. -func MakeMongoManipulator(cfg conf.MongoConf, hasher sharder.Hasher, additionalOptions ...manipmongo.Option) manipulate.TransactionalManipulator { +func MakeMongoManipulator(cfg conf.MongoConf, hasher sharder.Hasher, model elemental.ModelManager, additionalOptions ...manipmongo.Option) manipulate.TransactionalManipulator { var consistency manipulate.ReadConsistency switch cfg.MongoConsistency { @@ -79,8 +82,7 @@ func MakeMongoManipulator(cfg conf.MongoConf, hasher sharder.Hasher, additionalO manipmongo.OptionCredentials(cfg.MongoUser, cfg.MongoPassword, cfg.MongoAuthDB), manipmongo.OptionConnectionPoolLimit(cfg.MongoPoolSize), manipmongo.OptionDefaultReadConsistencyMode(consistency), - manipmongo.OptionTranslateKeysFromModelManager(api.Manager()), - manipmongo.OptionSharder(sharder.New(hasher)), + manipmongo.OptionTranslateKeysFromModelManager(model), manipmongo.OptionDefaultRetryFunc(func(i manipulate.RetryInfo) error { info := i.(manipmongo.RetryInfo) zap.L().Debug("mongo manipulator retry", @@ -95,6 +97,13 @@ func MakeMongoManipulator(cfg conf.MongoConf, hasher sharder.Hasher, additionalO additionalOptions..., ) + if hasher != nil { + opts = append( + opts, + manipmongo.OptionSharder(sharder.New(hasher)), + ) + } + tlscfg, err := cfg.TLSConfig() if err != nil { zap.L().Fatal("Unable to prepare TLS config for mongodb", zap.Error(err)) @@ -185,3 +194,35 @@ func MakeA3SManipulator(ctx context.Context, a3sConfig conf.A3SClientConf) (mani return m, nil } + +// MakeA3SRemoteAuth is a convenience function that will return +// ready to user Authenticator and Authorizers for a bahamut server. +// It uses the given manipulator to talk to the instance of a3s. +func MakeA3SRemoteAuth( + ctx context.Context, + m manipulate.Manipulator, + requiredIssuer string, + requiredAudience string, +) (*authenticator.Authenticator, authorizer.Authorizer, error) { + + jwks, err := token.NewRemoteJWKS( + ctx, + maniphttp.ExtractClient(m), + fmt.Sprintf("%s/.well-known/jwks.json", maniphttp.ExtractEndpoint(m)), + ) + if err != nil { + return nil, nil, fmt.Errorf("unable to retrieve a3s JWT: %w", err) + } + + return authenticator.New( + jwks, + requiredIssuer, + requiredAudience, + ), + authorizer.NewRemote( + ctx, + m, + permissions.NewRemoteRetriever(m), + ), + nil +} diff --git a/pkgs/importing/hash.go b/pkgs/importing/hash.go index 6e9703a..63d426f 100644 --- a/pkgs/importing/hash.go +++ b/pkgs/importing/hash.go @@ -85,8 +85,12 @@ func sanitize(obj elemental.AttributeSpecifiable, manager elemental.ModelManager // If the type is a ref, we recursively sanitize the nested object. if spec.Type == "ref" { - fmt.Printf("%#v\n\n", v) - m, err := sanitize(v.(elemental.AttributeSpecifiable), manager) + vv := manager.IdentifiableFromString(spec.SubType) + if err := mapstructure.Decode(v, vv); err != nil { + return nil, err + } + + m, err := sanitize(vv.(elemental.AttributeSpecifiable), manager) if err != nil { return nil, err }