From 9b91c2d4b664fec5263c94a5d08d2de889ba98bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20S=C3=B6derlund?= Date: Sun, 16 May 2021 11:51:21 +0200 Subject: [PATCH] feat: initial implementation of CEL expression evaluation Annotated authorization policies on the gRPC methods are code-generated into a middleware that uses CEL Go to evaluate the policies on requests and responses. --- cmd/iamctl/go.sum | 7 + .../examplecmd/exampleservercmd/server.go | 4 +- .../internal/geniam/authorization.go | 189 ++++++ .../internal/geniam/descriptor.go | 199 ++----- cmd/protoc-gen-go-iam/internal/geniam/gen.go | 35 +- .../internal/geniam/helpers.go | 12 - cmd/protoc-gen-go-iam/main.go | 5 - go.mod | 1 + go.sum | 11 + iamauthz/after.go | 93 +++ iamauthz/before.go | 122 ++++ iamauthz/functions.go | 209 +++++++ iamauthz/resolve.go | 16 + iamexample/authorization.go | 322 +---------- iamexample/iamexampledata/roles.go | 24 - iamexample/server_test.go | 16 +- iamreflect/iamdescriptor.go | 154 +++++ iamreflect/resourcepermission.go | 22 + iamspanner/server.go | 51 ++ .../iam/example/v1/freight_service.pb.go | 15 +- .../iam/example/v1/freight_service_iam.go | 405 ------------- .../iam/example/v1/freight_service_iam.pb.go | 539 ++++++++++++++++++ proto/gen/einride/iam/v1/caller.pb.go | 32 +- .../iam/example/v1/freight_service.proto | 5 +- proto/src/einride/iam/v1/caller.proto | 4 +- 25 files changed, 1528 insertions(+), 964 deletions(-) create mode 100644 cmd/protoc-gen-go-iam/internal/geniam/authorization.go create mode 100644 iamauthz/after.go create mode 100644 iamauthz/before.go create mode 100644 iamauthz/functions.go create mode 100644 iamauthz/resolve.go delete mode 100644 iamexample/iamexampledata/roles.go create mode 100644 iamreflect/iamdescriptor.go create mode 100644 iamreflect/resourcepermission.go delete mode 100644 proto/gen/einride/iam/example/v1/freight_service_iam.go create mode 100644 proto/gen/einride/iam/example/v1/freight_service_iam.pb.go diff --git a/cmd/iamctl/go.sum b/cmd/iamctl/go.sum index 7620f243..14ca970d 100644 --- a/cmd/iamctl/go.sum +++ b/cmd/iamctl/go.sum @@ -47,6 +47,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -128,6 +130,9 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.7.3 h1:8v9BSN0avuGwrHFKNCjfiQ/CE6+D6sW+BDyOVoEeP6o= +github.com/google/cel-go v0.7.3/go.mod h1:4EtyFAHT5xNr0Msu0MJjyGxPUgdr9DlcaPyzLt/kkt8= +github.com/google/cel-spec v0.5.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -274,6 +279,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -589,6 +595,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= diff --git a/cmd/iamctl/internal/examplecmd/exampleservercmd/server.go b/cmd/iamctl/internal/examplecmd/exampleservercmd/server.go index aa2f8bac..3d4fb170 100644 --- a/cmd/iamctl/internal/examplecmd/exampleservercmd/server.go +++ b/cmd/iamctl/internal/examplecmd/exampleservercmd/server.go @@ -63,8 +63,8 @@ func newServer(spannerClient *spanner.Client) (iamexamplev1.FreightServiceServer }, } freightServerAuthorization := &iamexample.Authorization{ - Next: freightServer, - IAM: iamServer, + Next: freightServer, + IAMServer: iamServer, } return freightServerAuthorization, nil } diff --git a/cmd/protoc-gen-go-iam/internal/geniam/authorization.go b/cmd/protoc-gen-go-iam/internal/geniam/authorization.go new file mode 100644 index 00000000..8d30dd2f --- /dev/null +++ b/cmd/protoc-gen-go-iam/internal/geniam/authorization.go @@ -0,0 +1,189 @@ +package geniam + +import ( + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" + "google.golang.org/protobuf/compiler/protogen" +) + +type authorizationCodeGenerator struct { + gen *protogen.Plugin + file *protogen.File + service *protogen.Service +} + +func (c authorizationCodeGenerator) ConstructorGoName() string { + return "New" + c.StructGoName() +} + +func (c authorizationCodeGenerator) StructGoName() string { + return c.service.GoName + "Authorization" +} + +func (c authorizationCodeGenerator) GenerateCode(g *protogen.GeneratedFile) { + c.generateConstructor(g) + c.generateStruct(g) +} + +func (c authorizationCodeGenerator) GeneratesCode() bool { + for _, method := range c.service.Methods { + if getMethodAuthorizationOptions(method) != nil { + return true + } + } + return false +} + +func (c authorizationCodeGenerator) serverGoName() string { + return c.service.GoName + "Server" +} + +func (c authorizationCodeGenerator) generateStruct(g *protogen.GeneratedFile) { + g.P() + g.P("type ", c.StructGoName(), " struct {") + g.P("next ", c.serverGoName()) + for _, method := range c.service.Methods { + switch getMethodAuthorizationOptions(method).GetStrategy().(type) { + case *iamv1.MethodAuthorizationOptions_Before: + beforeMethodAuthorization := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "BeforeMethodAuthorization", + }) + g.P("before", method.GoName, " *", beforeMethodAuthorization) + case *iamv1.MethodAuthorizationOptions_After: + afterMethodAuthorization := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "AfterMethodAuthorization", + }) + g.P("after", method.GoName, " *", afterMethodAuthorization) + } + } + g.P("}") + for _, method := range c.service.Methods { + contextContext := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "context", + GoName: "Context", + }) + g.P() + g.P("func (a *", c.StructGoName(), ") ", method.GoName, "(") + g.P("ctx ", contextContext, ",") + g.P("request *", method.Input.GoIdent, ",") + g.P(") (*", method.Output.GoIdent, ", error) {") + switch getMethodAuthorizationOptions(method).GetStrategy().(type) { + case *iamv1.MethodAuthorizationOptions_Before: + g.P("ctx, err := a.before", method.GoName, ".AuthorizeRequest(ctx, request)") + g.P("if err != nil {") + g.P("return nil, err") + g.P("}") + g.P("return a.next.", method.GoName, "(ctx, request)") + case *iamv1.MethodAuthorizationOptions_After: + g.P("response, err := a.next.", method.GoName, "(ctx, request)") + g.P("_, errAuth := a.after", method.GoName, ".AuthorizeRequestAndResponse(ctx, request, response)") + g.P("if errAuth != nil {") + g.P("return nil, errAuth") + g.P("}") + g.P("return response, err") + case *iamv1.MethodAuthorizationOptions_Open: + authorize := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "Authorize", + }) + g.P(authorize, "(ctx)") + g.P("return a.next.", method.GoName, "(ctx, request)") + default: + statusError := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "google.golang.org/grpc/status", + GoName: "Error", + }) + codesInternal := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "google.golang.org/grpc/codes", + GoName: "Internal", + }) + g.P("return nil, ", statusError, "(", codesInternal, `, "authorization not configured")`) + } + g.P("}") + } +} + +func (c authorizationCodeGenerator) generateConstructor(g *protogen.GeneratedFile) { + memberResolver := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iammember", + GoName: "Resolver", + }) + permissionTester := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "PermissionTester", + }) + g.P() + g.P("// ", c.ConstructorGoName(), " creates a new authorization middleware for ", c.service.GoName, ".") + g.P("func ", c.ConstructorGoName(), "(") + g.P("next ", c.serverGoName(), ",") + g.P("permissionTester ", permissionTester, ",") + g.P("memberResolver ", memberResolver, ",") + g.P(") (*", c.StructGoName(), ", error) {") + g.P("var result ", c.StructGoName()) + g.P("result.next = next") + for _, method := range c.service.Methods { + switch getMethodAuthorizationOptions(method).GetStrategy().(type) { + case *iamv1.MethodAuthorizationOptions_Before, *iamv1.MethodAuthorizationOptions_After: + globalFiles := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "google.golang.org/protobuf/reflect/protoregistry", + GoName: "GlobalFiles", + }) + methodDescriptor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "google.golang.org/protobuf/reflect/protoreflect", + GoName: "MethodDescriptor", + }) + fmtErrorf := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "fmt", + GoName: "Errorf", + }) + methodDescriptorVar := "descriptor" + method.GoName + methodVar := "method" + method.GoName + methodName := method.Desc.FullName() + g.P(methodDescriptorVar, ", err := ", globalFiles, ".FindDescriptorByName(\"", methodName, "\")") + g.P("if err != nil {") + g.P( + "return nil, ", fmtErrorf, + `("new `, c.service.GoName, ` authorization: failed to find descriptor for `, method.Desc.Name(), `")`, + ) + g.P("}") + g.P(methodVar, ", ok := ", methodDescriptorVar, ".(", methodDescriptor, ")") + g.P("if !ok {") + g.P( + "return nil, ", fmtErrorf, + `("new `, c.service.GoName, ` authorization: got non-method descriptor for `, method.Desc.Name(), `")`, + ) + g.P("}") + switch getMethodAuthorizationOptions(method).GetStrategy().(type) { + case *iamv1.MethodAuthorizationOptions_Before: + constructor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "NewBeforeMethodAuthorization", + }) + g.P( + "before", method.GoName, ", err := ", constructor, + "(", methodVar, ", permissionTester, memberResolver)", + ) + g.P("if err != nil {") + g.P("return nil, ", fmtErrorf, `("new `, c.service.GoName, ` authorization: %w", err)`) + g.P("}") + g.P("result.before", method.GoName, " = before", method.GoName) + case *iamv1.MethodAuthorizationOptions_After: + constructor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamauthz", + GoName: "NewAfterMethodAuthorization", + }) + g.P( + "after", method.GoName, ", err := ", constructor, + "(", methodVar, ", permissionTester, memberResolver)", + ) + g.P("if err != nil {") + g.P("return nil, ", fmtErrorf, `("new `, c.service.GoName, ` authorization: %w", err)`) + g.P("}") + g.P("result.after", method.GoName, " = after", method.GoName) + } + } + } + g.P("return &result, nil") + g.P("}") +} diff --git a/cmd/protoc-gen-go-iam/internal/geniam/descriptor.go b/cmd/protoc-gen-go-iam/internal/geniam/descriptor.go index f149d560..77c60900 100644 --- a/cmd/protoc-gen-go-iam/internal/geniam/descriptor.go +++ b/cmd/protoc-gen-go-iam/internal/geniam/descriptor.go @@ -1,192 +1,67 @@ package geniam import ( - "strconv" - "google.golang.org/protobuf/compiler/protogen" ) -type iamServiceDescriptorCodeGenerator struct { +type descriptorCodeGenerator struct { gen *protogen.Plugin file *protogen.File service *protogen.Service } -func (c iamServiceDescriptorCodeGenerator) GlobalFunctionGoName() string { - return c.service.GoName + "IAM" -} - -func (c iamServiceDescriptorCodeGenerator) ServiceDescriptorInterfaceGoName() string { - return c.service.GoName + "IAMDescriptor" -} - -func (c iamServiceDescriptorCodeGenerator) ServiceDescriptorStructGoName() string { - return private(c.service.GoName + "IAMDescriptor") -} - -func (c iamServiceDescriptorCodeGenerator) ServiceDescriptorVariableGoName() string { - return fileVarName(c.file, "iamDescriptor_"+c.service.GoName) -} - -func (c iamServiceDescriptorCodeGenerator) ServiceDescriptorVariableInitFunctionGoName() string { - return fileVarName(c.file, "init_iamDescriptor_"+c.service.GoName) -} - -func (c iamServiceDescriptorCodeGenerator) GenerateCode(g *protogen.GeneratedFile) { - c.generateGlobalFunction(g) - c.generateServiceDescriptorInterface(g) - c.generateServiceDescriptorStruct(g) - c.generateServiceDescriptorVariable(g) +func (c descriptorCodeGenerator) ConstructorGoName() string { + return "New" + c.service.GoName + "IAMDescriptor" } -func (c iamServiceDescriptorCodeGenerator) GenerateInitFunctionCalls(g *protogen.GeneratedFile) { - g.P(c.ServiceDescriptorVariableInitFunctionGoName(), "()") -} - -func (c iamServiceDescriptorCodeGenerator) generateGlobalFunction(g *protogen.GeneratedFile) { - g.P() - g.P( - "// ", c.GlobalFunctionGoName(), - " returns a descriptor for the ", c.service.Desc.Name(), " IAM configuration.", - ) - g.P("func ", c.GlobalFunctionGoName(), "() ", c.ServiceDescriptorInterfaceGoName(), " {") - g.P("return &", c.ServiceDescriptorVariableGoName()) - g.P("}") -} - -func (c iamServiceDescriptorCodeGenerator) generateServiceDescriptorInterface(g *protogen.GeneratedFile) { - g.P() - g.P( - "// ", c.ServiceDescriptorInterfaceGoName(), - " describes the ", c.service.Desc.Name(), " IAM configuration.", - ) - g.P("type ", c.ServiceDescriptorInterfaceGoName(), " interface {") - if getPredefinedRoles(c.service) != nil { - g.Unskip() - iamv1Roles := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "Roles", - }) - g.P("PredefinedRoles() *", iamv1Roles) - } - for _, method := range c.service.Methods { - if getMethodAuthorizationOptions(method) == nil { - continue - } - g.Unskip() - iamv1MethodAuthorizationOptions := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "MethodAuthorizationOptions", - }) - g.P(method.GoName, "() *", iamv1MethodAuthorizationOptions) - } - g.P("}") +func (c descriptorCodeGenerator) GenerateCode(g *protogen.GeneratedFile) { + c.generateConstructor(g) } -func (c iamServiceDescriptorCodeGenerator) generateServiceDescriptorStruct(g *protogen.GeneratedFile) { - g.P() - g.P("type ", c.ServiceDescriptorStructGoName(), " struct {") +func (c descriptorCodeGenerator) GeneratesCode() bool { if getPredefinedRoles(c.service) != nil { - iamv1Roles := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "Roles", - }) - g.P("predefinedRoles *", iamv1Roles) + return true } for _, method := range c.service.Methods { - if getMethodAuthorizationOptions(method) == nil { - continue + if getMethodAuthorizationOptions(method) != nil { + return true } - iamv1MethodAuthorizationOptions := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "MethodAuthorizationOptions", - }) - g.P(private(method.GoName), " *", iamv1MethodAuthorizationOptions) - } - g.P("}") - if getPredefinedRoles(c.service) != nil { - iamv1Roles := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "Roles", - }) - g.P() - g.P("func (d *", c.ServiceDescriptorStructGoName(), ") PredefinedRoles() *", iamv1Roles, " {") - g.P("return d.predefinedRoles") - g.P("}") - } - for _, method := range c.service.Methods { - if getMethodAuthorizationOptions(method) == nil { - continue - } - methodOptions := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "MethodAuthorizationOptions", - }) - g.P() - g.P("func (d *", c.ServiceDescriptorStructGoName(), ") ", method.GoName, "() *", methodOptions, " {") - g.P("return d.", private(method.GoName)) - g.P("}") } + return false } -func (c iamServiceDescriptorCodeGenerator) generateServiceDescriptorVariable(g *protogen.GeneratedFile) { +func (c descriptorCodeGenerator) generateConstructor(g *protogen.GeneratedFile) { + iamDescriptor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamreflect", + GoName: "IAMDescriptor", + }) + newIAMDescriptor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "go.einride.tech/iam/iamreflect", + GoName: "NewIAMDescriptor", + }) globalFiles := g.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "google.golang.org/protobuf/reflect/protoregistry", GoName: "GlobalFiles", }) - getExtension := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "google.golang.org/protobuf/proto", - GoName: "GetExtension", + serviceDescriptor := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "google.golang.org/protobuf/reflect/protoreflect", + GoName: "ServiceDescriptor", + }) + fmtErrorf := g.QualifiedGoIdent(protogen.GoIdent{ + GoImportPath: "fmt", + GoName: "Errorf", }) g.P() - g.P("var ", c.ServiceDescriptorVariableGoName(), " ", c.ServiceDescriptorStructGoName()) - g.P() - g.P("func ", c.ServiceDescriptorVariableInitFunctionGoName(), "() {") - if getPredefinedRoles(c.service) != nil { - roles := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "Roles", - }) - ePredefinedRoles := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "E_PredefinedRoles", - }) - g.P("// Init predefined roles.") - g.P("serviceDesc, err := ", globalFiles, ".FindDescriptorByName(") - g.P(strconv.Quote(string(c.service.Desc.FullName())), ",") - g.P(")") - g.P("if err != nil {") - g.P("panic(\"unable to find service descriptor ", c.service.Desc.FullName(), "\")") - g.P("}") - g.P(c.ServiceDescriptorVariableGoName(), ".predefinedRoles = ", getExtension, "(") - g.P("serviceDesc.Options(),") - g.P(ePredefinedRoles, ",") - g.P(").(*", roles, ")") - } - for _, method := range c.service.Methods { - if getMethodAuthorizationOptions(method) == nil { - continue - } - methodOptions := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "MethodAuthorizationOptions", - }) - eMethodAuthorization := g.QualifiedGoIdent(protogen.GoIdent{ - GoImportPath: "go.einride.tech/iam/proto/gen/einride/iam/v1", - GoName: "E_MethodAuthorization", - }) - g.P("// Init ", method.Desc.Name(), " authorization options.") - g.P("methodDesc", method.Desc.Name(), ", err := ", globalFiles, ".FindDescriptorByName(") - g.P(strconv.Quote(string(method.Desc.FullName())), ",") - g.P(")") - g.P("if err != nil {") - g.P("panic(\"unable to find method descriptor ", method.Desc.FullName(), "\")") - g.P("}") - g.P(c.ServiceDescriptorVariableGoName(), ".", private(method.GoName), " = ", getExtension, "(") - g.P("methodDesc", method.Desc.Name(), ".Options(),") - g.P(eMethodAuthorization, ",") - g.P(").(*", methodOptions, ")") - // TODO: For methods with per-resource permissions, resolve the resource descriptors here. - } + g.P("// ", c.ConstructorGoName(), " returns a new ", c.service.GoName, " IAM descriptor.") + g.P("func ", c.ConstructorGoName(), "() (*", iamDescriptor, ", error) {") + g.P("descriptor, err := ", globalFiles, ".FindDescriptorByName(\"", c.service.Desc.FullName(), "\")") + g.P("if err != nil {") + g.P("return nil, ", fmtErrorf, `("new `, c.service.GoName, ` IAM descriptor: %w", err)`) + g.P("}") + g.P("service, ok := descriptor.(", serviceDescriptor, ")") + g.P("if !ok {") + g.P("return nil, ", fmtErrorf, `("new `, c.service.GoName, `IAM descriptor: got non-service descriptor")`) + g.P("}") + g.P("return ", newIAMDescriptor, "(service, ", globalFiles, ")") g.P("}") } diff --git a/cmd/protoc-gen-go-iam/internal/geniam/gen.go b/cmd/protoc-gen-go-iam/internal/geniam/gen.go index 3bc36824..6ffb6379 100644 --- a/cmd/protoc-gen-go-iam/internal/geniam/gen.go +++ b/cmd/protoc-gen-go-iam/internal/geniam/gen.go @@ -1,31 +1,38 @@ package geniam import ( + "fmt" + "google.golang.org/protobuf/compiler/protogen" ) -const Version = "development" - -const generatedFilenameSuffix = "_iam.go" +const generatedFilenameSuffix = "_iam.pb.go" func GenerateFile(gen *protogen.Plugin, f *protogen.File) { filename := f.GeneratedFilenamePrefix + generatedFilenameSuffix g := gen.NewGeneratedFile(filename, f.GoImportPath) g.Skip() + g.P("// Code generated by protoc-gen-go-iam. DO NOT EDIT.") + g.P("// versions:") + protocVersion := "(unknown)" + if v := gen.Request.GetCompilerVersion(); v != nil { + protocVersion = fmt.Sprintf("v%v.%v.%v", v.GetMajor(), v.GetMinor(), v.GetPatch()) + if s := v.GetSuffix(); s != "" { + protocVersion += "-" + s + } + } + g.P("// \tprotoc ", protocVersion) + g.P() g.P("package ", f.GoPackageName) g.P() for _, service := range f.Services { - descriptor := iamServiceDescriptorCodeGenerator{gen: gen, file: f, service: service} + descriptor := descriptorCodeGenerator{gen: gen, file: f, service: service} + authorization := authorizationCodeGenerator{gen: gen, file: f, service: service} + if !descriptor.GeneratesCode() && !authorization.GeneratesCode() { + continue + } + g.Unskip() descriptor.GenerateCode(g) + authorization.GenerateCode(g) } - g.P() - g.P("func init() {") - g.P("// This init function runs after the init function of ", f.GeneratedFilenamePrefix, ".pb.go.") - g.P("// We depend on the Go initialization order to ensure this.") - g.P("// See: https://golang.org/ref/spec#Package_initialization") - for _, service := range f.Services { - descriptor := iamServiceDescriptorCodeGenerator{gen: gen, file: f, service: service} - descriptor.GenerateInitFunctionCalls(g) - } - g.P("}") } diff --git a/cmd/protoc-gen-go-iam/internal/geniam/helpers.go b/cmd/protoc-gen-go-iam/internal/geniam/helpers.go index a6abb9c7..aa3159c0 100644 --- a/cmd/protoc-gen-go-iam/internal/geniam/helpers.go +++ b/cmd/protoc-gen-go-iam/internal/geniam/helpers.go @@ -1,9 +1,6 @@ package geniam import ( - "strings" - "unicode/utf8" - iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/proto" @@ -16,12 +13,3 @@ func getPredefinedRoles(service *protogen.Service) *iamv1.Roles { func getMethodAuthorizationOptions(method *protogen.Method) *iamv1.MethodAuthorizationOptions { return proto.GetExtension(method.Desc.Options(), iamv1.E_MethodAuthorization).(*iamv1.MethodAuthorizationOptions) } - -func private(s string) string { - _, n := utf8.DecodeRuneInString(s) - return strings.ToLower(s[:n]) + s[n:] -} - -func fileVarName(f *protogen.File, suffix string) string { - return private(f.GoDescriptorIdent.GoName) + "_" + suffix -} diff --git a/cmd/protoc-gen-go-iam/main.go b/cmd/protoc-gen-go-iam/main.go index 1d027cd8..1864fa42 100644 --- a/cmd/protoc-gen-go-iam/main.go +++ b/cmd/protoc-gen-go-iam/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "path/filepath" "go.einride.tech/iam/cmd/protoc-gen-go-iam/internal/geniam" "google.golang.org/protobuf/compiler/protogen" @@ -12,10 +11,6 @@ import ( const docURL = "https://pkg.go.dev/go.einride.tech/iam" func main() { - if len(os.Args) == 2 && os.Args[1] == "--version" { - _, _ = fmt.Fprintf(os.Stdout, "%v %v\n", filepath.Base(os.Args[0]), geniam.Version) - os.Exit(0) - } if len(os.Args) == 2 && os.Args[1] == "--help" { _, _ = fmt.Fprintf(os.Stdout, "See %s for usage information.\n", docURL) os.Exit(0) diff --git a/go.mod b/go.mod index 5ec8cf4a..a5d5b9a9 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( cloud.google.com/go/spanner v1.18.0 + github.com/google/cel-go v0.7.3 go.einride.tech/aip v0.38.0 go.einride.tech/spanner-aip v0.34.0 google.golang.org/api v0.46.0 diff --git a/go.sum b/go.sum index 878ab64a..3c4edb34 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -50,6 +52,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -94,6 +97,9 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.7.3 h1:8v9BSN0avuGwrHFKNCjfiQ/CE6+D6sW+BDyOVoEeP6o= +github.com/google/cel-go v0.7.3/go.mod h1:4EtyFAHT5xNr0Msu0MJjyGxPUgdr9DlcaPyzLt/kkt8= +github.com/google/cel-spec v0.5.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -140,14 +146,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -433,6 +442,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -486,6 +496,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/iamauthz/after.go b/iamauthz/after.go new file mode 100644 index 00000000..15a66822 --- /dev/null +++ b/iamauthz/after.go @@ -0,0 +1,93 @@ +package iamauthz + +import ( + "context" + "fmt" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "go.einride.tech/iam/iammember" + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type AfterMethodAuthorization struct { + methodAuthorizationOptions *iamv1.MethodAuthorizationOptions + memberResolver iammember.Resolver + program cel.Program +} + +func NewAfterMethodAuthorization( + method protoreflect.MethodDescriptor, + permissionTester PermissionTester, + memberResolver iammember.Resolver, +) (*AfterMethodAuthorization, error) { + methodAuthorizationOptions := proto.GetExtension( + method.Options(), iamv1.E_MethodAuthorization, + ).(*iamv1.MethodAuthorizationOptions) + if methodAuthorizationOptions == nil { + return nil, fmt.Errorf("missing method_authorization annotation") + } + afterStrategy, ok := methodAuthorizationOptions.Strategy.(*iamv1.MethodAuthorizationOptions_After) + if !ok { + return nil, fmt.Errorf("strategy must be 'after'") + } + caller := (&iamv1.Caller{}).ProtoReflect().Descriptor() + fns := NewFunctions(methodAuthorizationOptions, permissionTester) + env, err := cel.NewEnv( + cel.TypeDescs(collectTypeDescs(caller, method.Input(), method.Output())), + cel.Declarations( + decls.NewVar("caller", decls.NewObjectType(string(caller.FullName()))), + decls.NewVar("request", decls.NewObjectType(string(method.Input().FullName()))), + decls.NewVar("response", decls.NewObjectType(string(method.Output().FullName()))), + ), + cel.Declarations(fns.Declarations()...), + ) + if err != nil { + return nil, err + } + ast, issues := env.Compile(afterStrategy.After.Expression) + if issues.Err() != nil { + return nil, issues.Err() + } + program, err := env.Program(ast, cel.Functions(fns.Functions()...)) + if err != nil { + return nil, err + } + return &AfterMethodAuthorization{ + methodAuthorizationOptions: methodAuthorizationOptions, + memberResolver: memberResolver, + program: program, + }, nil +} + +func (a *AfterMethodAuthorization) AuthorizeRequestAndResponse( + ctx context.Context, + request proto.Message, + response proto.Message, +) (context.Context, error) { + Authorize(ctx) + ctx, members, err := a.memberResolver.ResolveIAMMembers(ctx) + if err != nil { + return nil, err + } + val, _, err := a.program.Eval(map[string]interface{}{ + "caller": &iamv1.Caller{Members: members}, + "request": request, + "response": response, + }) + if err != nil { + return nil, err + } + boolVal, ok := val.Value().(bool) + if !ok { + return nil, status.Error(codes.Internal, "authorization policy returned non-bool result") + } + if !boolVal { + return nil, status.Error(codes.PermissionDenied, a.methodAuthorizationOptions.GetAfter().GetDescription()) + } + return ctx, nil +} diff --git a/iamauthz/before.go b/iamauthz/before.go new file mode 100644 index 00000000..762bbdee --- /dev/null +++ b/iamauthz/before.go @@ -0,0 +1,122 @@ +package iamauthz + +import ( + "context" + "fmt" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/checker/decls" + "go.einride.tech/iam/iammember" + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" +) + +type BeforeMethodAuthorization struct { + methodAuthorizationOptions *iamv1.MethodAuthorizationOptions + memberResolver iammember.Resolver + program cel.Program +} + +func NewBeforeMethodAuthorization( + method protoreflect.MethodDescriptor, + permissionTester PermissionTester, + memberResolver iammember.Resolver, +) (*BeforeMethodAuthorization, error) { + methodAuthorizationOptions := proto.GetExtension( + method.Options(), iamv1.E_MethodAuthorization, + ).(*iamv1.MethodAuthorizationOptions) + if methodAuthorizationOptions == nil { + return nil, fmt.Errorf("missing method_authorization annotation") + } + beforeStrategy, ok := methodAuthorizationOptions.Strategy.(*iamv1.MethodAuthorizationOptions_Before) + if !ok { + return nil, fmt.Errorf("strategy must be 'before'") + } + fns := NewFunctions(methodAuthorizationOptions, permissionTester) + caller := (&iamv1.Caller{}).ProtoReflect().Descriptor() + env, err := cel.NewEnv( + cel.TypeDescs(collectTypeDescs(caller, method.Input())), + cel.Declarations( + decls.NewVar("caller", decls.NewObjectType(string(caller.FullName()))), + decls.NewVar("request", decls.NewObjectType(string(method.Input().FullName()))), + ), + cel.Declarations(fns.Declarations()...), + ) + if err != nil { + return nil, err + } + ast, issues := env.Compile(beforeStrategy.Before.Expression) + if issues.Err() != nil { + return nil, issues.Err() + } + program, err := env.Program(ast, cel.Functions(fns.Functions()...)) + if err != nil { + return nil, err + } + return &BeforeMethodAuthorization{ + methodAuthorizationOptions: methodAuthorizationOptions, + memberResolver: memberResolver, + program: program, + }, nil +} + +func (a *BeforeMethodAuthorization) AuthorizeRequest( + ctx context.Context, + request proto.Message, +) (context.Context, error) { + Authorize(ctx) + ctx, members, err := a.memberResolver.ResolveIAMMembers(ctx) + if err != nil { + return nil, err + } + val, _, err := a.program.Eval(map[string]interface{}{ + "caller": &iamv1.Caller{Members: members}, + "request": request, + }) + if err != nil { + return nil, err + } + boolVal, ok := val.Value().(bool) + if !ok { + return nil, status.Error(codes.Internal, "authorization policy returned non-bool result") + } + if !boolVal { + return nil, status.Error(codes.PermissionDenied, a.methodAuthorizationOptions.GetBefore().GetDescription()) + } + return ctx, nil +} + +func collectTypeDescs(messages ...protoreflect.MessageDescriptor) *protoregistry.Files { + fdMap := map[string]protoreflect.FileDescriptor{} + for _, message := range messages { + parentFile := message.ParentFile() + fdMap[parentFile.Path()] = parentFile + // Initialize list of dependencies + deps := make([]protoreflect.FileImport, parentFile.Imports().Len()) + for i := 0; i < parentFile.Imports().Len(); i++ { + deps[i] = parentFile.Imports().Get(i) + } + // Expand list for new dependencies + for i := 0; i < len(deps); i++ { + dep := deps[i] + if _, found := fdMap[dep.Path()]; found { + continue + } + fdMap[dep.Path()] = dep.FileDescriptor + for j := 0; j < dep.FileDescriptor.Imports().Len(); j++ { + deps = append(deps, dep.FileDescriptor.Imports().Get(j)) + } + } + } + var registry protoregistry.Files + for _, fd := range fdMap { + if err := registry.RegisterFile(fd); err != nil { + panic(err) + } + } + return ®istry +} diff --git a/iamauthz/functions.go b/iamauthz/functions.go new file mode 100644 index 00000000..1fd7e2d5 --- /dev/null +++ b/iamauthz/functions.go @@ -0,0 +1,209 @@ +package iamauthz + +import ( + "context" + "reflect" + "time" + + "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/interpreter/functions" + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" + expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" +) + +const ( + testFunction = "test" + testFunctionOverload = "test_caller_string_bool" + testAllFunction = "test_all" + testAllFunctionOverload = "test_all_caller_strings_bool" + testAnyFunction = "test_any" + testAnyFunctionOverload = "test_any_caller_strings_bool" +) + +type PermissionTester interface { + TestResourcePermission(context.Context, []string, string, string) (bool, error) + TestResourcePermissions(context.Context, []string, map[string]string) (map[string]bool, error) +} + +type Functions struct { + options *iamv1.MethodAuthorizationOptions + tester PermissionTester +} + +func NewFunctions(options *iamv1.MethodAuthorizationOptions, tester PermissionTester) *Functions { + return &Functions{ + options: options, + tester: tester, + } +} + +func (f *Functions) Declarations() []*expr.Decl { + caller := (&iamv1.Caller{}).ProtoReflect().Descriptor() + return []*expr.Decl{ + decls.NewFunction( + testFunction, + decls.NewOverload( + testFunctionOverload, + []*expr.Type{ + decls.NewObjectType(string(caller.FullName())), + decls.String, + }, + decls.Bool, + ), + ), + decls.NewFunction( + testAllFunction, + decls.NewOverload( + testAllFunctionOverload, + []*expr.Type{ + decls.NewObjectType(string(caller.FullName())), + decls.NewListType(decls.String), + }, + decls.Bool, + ), + ), + decls.NewFunction( + testAnyFunction, + decls.NewOverload( + testAnyFunctionOverload, + []*expr.Type{ + decls.NewObjectType(string(caller.FullName())), + decls.NewListType(decls.String), + }, + decls.Bool, + ), + ), + } +} + +func (f *Functions) Functions() []*functions.Overload { + return []*functions.Overload{ + {Operator: testFunctionOverload, Binary: f.testCallerResource}, + {Operator: testAllFunctionOverload, Binary: f.testAllCallerResources}, + {Operator: testAnyFunctionOverload, Binary: f.testAnyCallerResources}, + } +} + +func (f *Functions) testCallerResource(callerVal ref.Val, resourceVal ref.Val) ref.Val { + caller, ok := callerVal.Value().(*iamv1.Caller) + if !ok { + return types.NewErr("test: unexpected type of arg 1, expected %T but got %T", &iamv1.Caller{}, caller) + } + resource, ok := resourceVal.Value().(string) + if !ok { + return types.NewErr("test: unexpected type of arg 2, expected string but got %T", resource) + } + permission, err := ResolvePermissionForResource(f.options, resource) + if err != nil { + return types.NewErr(err.Error()) + } + // TODO: When cel-go supports async functions, use the caller context here. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if result, err := f.tester.TestResourcePermission( + ctx, + caller.Members, + resource, + permission, + ); err != nil { + return types.NewErr("test: error testing permission: %v", err) + } else if !result { + return types.False + } else { + return types.True + } +} + +func (f *Functions) testAllCallerResources(callerVal, resourcesVal ref.Val) ref.Val { + caller, ok := callerVal.Value().(*iamv1.Caller) + if !ok { + return types.NewErr("test_all: unexpected type of arg 1, expected %T but got %T", &iamv1.Caller{}, caller) + } + convertedResources, err := resourcesVal.ConvertToNative(reflect.TypeOf([]string(nil))) + if err != nil { + return types.NewErr("test_all: unexpected type of arg 2, expected []string but got %T", resourcesVal) + } + resources, ok := convertedResources.([]string) + if !ok { + return types.NewErr("test_all: unexpected type of arg 2, expected []string but got %T", resourcesVal) + } + if len(resources) == 0 { + return types.False + } + resourcePermissions := make(map[string]string, len(resources)) + for _, resource := range resources { + permission, err := ResolvePermissionForResource(f.options, resource) + if err != nil { + return types.NewErr(err.Error()) + } + resourcePermissions[resource] = permission + } + // TODO: When cel-go supports async functions, use the caller context here. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if result, err := f.tester.TestResourcePermissions( + ctx, + caller.Members, + resourcePermissions, + ); err != nil { + return types.NewErr("test: error testing permission: %v", err) + } else { + if len(result) != len(resources) { + return types.False + } + for _, ok := range result { + if !ok { + return types.False + } + } + return types.True + } +} + +func (f *Functions) testAnyCallerResources(callerVal, resourcesVal ref.Val) ref.Val { + caller, ok := callerVal.Value().(*iamv1.Caller) + if !ok { + return types.NewErr("test_any: unexpected type of arg 1, expected %T but got %T", &iamv1.Caller{}, caller) + } + convertedResources, err := resourcesVal.ConvertToNative(reflect.TypeOf([]string(nil))) + if err != nil { + return types.NewErr("test_any: unexpected type of arg 2, expected []string but got %T", resourcesVal) + } + resources, ok := convertedResources.([]string) + if !ok { + return types.NewErr("test_any: unexpected type of arg 2, expected []string but got %T", resourcesVal) + } + if len(resources) == 0 { + return types.False + } + resourcePermissions := make(map[string]string, len(resources)) + for _, resource := range resources { + permission, err := ResolvePermissionForResource(f.options, resource) + if err != nil { + return types.NewErr(err.Error()) + } + resourcePermissions[resource] = permission + } + // TODO: When cel-go supports async functions, use the caller context here. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if result, err := f.tester.TestResourcePermissions( + ctx, + caller.Members, + resourcePermissions, + ); err != nil { + return types.NewErr("test: error testing permission: %v", err) + } else { + if len(result) != len(resources) { + return types.False + } + for _, ok := range result { + if ok { + return types.True + } + } + return types.False + } +} diff --git a/iamauthz/resolve.go b/iamauthz/resolve.go new file mode 100644 index 00000000..06bb10d5 --- /dev/null +++ b/iamauthz/resolve.go @@ -0,0 +1,16 @@ +package iamauthz + +import ( + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" +) + +func ResolvePermissionForResource(options *iamv1.MethodAuthorizationOptions, resource string) (string, error) { + switch permissions := options.Permissions.(type) { + case *iamv1.MethodAuthorizationOptions_Permission: + return permissions.Permission, nil + case *iamv1.MethodAuthorizationOptions_ResourcePermissions: + return "", nil + default: + return "", nil + } +} diff --git a/iamexample/authorization.go b/iamexample/authorization.go index db968556..24c4e8aa 100644 --- a/iamexample/authorization.go +++ b/iamexample/authorization.go @@ -3,234 +3,29 @@ package iamexample import ( "context" - "go.einride.tech/aip/resourcename" "go.einride.tech/iam/iamauthz" - "go.einride.tech/iam/iamresource" + "go.einride.tech/iam/iamreflect" "go.einride.tech/iam/iamspanner" iamexamplev1 "go.einride.tech/iam/proto/gen/einride/iam/example/v1" - "google.golang.org/genproto/googleapis/iam/admin/v1" - "google.golang.org/genproto/googleapis/iam/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type Authorization struct { - iam.UnimplementedIAMPolicyServer - admin.UnimplementedIAMServer - Next iamexamplev1.FreightServiceServer - IAM *iamspanner.IAMServer + *iamexamplev1.FreightServiceAuthorization + Next iamexamplev1.FreightServiceServer + IAMServer *iamspanner.IAMServer + IAMDescriptor *iamreflect.IAMDescriptor } var _ iamexamplev1.FreightServiceServer = &Authorization{} -func (a *Authorization) GetShipper( - ctx context.Context, - request *iamexamplev1.GetShipperRequest, -) (*iamexamplev1.Shipper, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().GetShipper().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.GetShipper(ctx, request) -} - -func (a *Authorization) ListShippers( - ctx context.Context, - request *iamexamplev1.ListShippersRequest, -) (*iamexamplev1.ListShippersResponse, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().ListShippers().GetPermission() - if err := a.require(ctx, permission, iamresource.Root); err != nil { - return nil, err - } - return a.Next.ListShippers(ctx, request) -} - -func (a *Authorization) CreateShipper( - ctx context.Context, - request *iamexamplev1.CreateShipperRequest, -) (*iamexamplev1.Shipper, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().CreateShipper().GetPermission() - if err := a.require(ctx, permission, iamresource.Root); err != nil { - return nil, err - } - return a.Next.CreateShipper(ctx, request) -} - -func (a *Authorization) UpdateShipper( - ctx context.Context, - request *iamexamplev1.UpdateShipperRequest, -) (*iamexamplev1.Shipper, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().UpdateShipper().GetPermission() - if err := a.require(ctx, permission, request.GetShipper().GetName()); err != nil { - return nil, err - } - return a.Next.UpdateShipper(ctx, request) -} - -func (a *Authorization) DeleteShipper( - ctx context.Context, - request *iamexamplev1.DeleteShipperRequest, -) (*iamexamplev1.Shipper, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().DeleteShipper().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.DeleteShipper(ctx, request) -} - -func (a *Authorization) GetSite( - ctx context.Context, - request *iamexamplev1.GetSiteRequest, -) (*iamexamplev1.Site, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().GetSite().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.GetSite(ctx, request) -} - -func (a *Authorization) ListSites( - ctx context.Context, - request *iamexamplev1.ListSitesRequest, -) (*iamexamplev1.ListSitesResponse, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().ListSites().GetPermission() - if err := a.require(ctx, permission, request.GetParent()); err != nil { - return nil, err - } - return a.Next.ListSites(ctx, request) -} - -func (a *Authorization) CreateSite( - ctx context.Context, - request *iamexamplev1.CreateSiteRequest, -) (*iamexamplev1.Site, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().CreateSite().GetPermission() - if err := a.require(ctx, permission, request.GetParent()); err != nil { - return nil, err - } - return a.Next.CreateSite(ctx, request) -} - -func (a *Authorization) UpdateSite( - ctx context.Context, - request *iamexamplev1.UpdateSiteRequest, -) (*iamexamplev1.Site, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().UpdateSite().GetPermission() - if err := a.require(ctx, permission, request.GetSite().GetName()); err != nil { - return nil, err - } - return a.Next.UpdateSite(ctx, request) -} - -func (a *Authorization) DeleteSite( - ctx context.Context, - request *iamexamplev1.DeleteSiteRequest, -) (*iamexamplev1.Site, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().DeleteSite().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.DeleteSite(ctx, request) -} - -func (a *Authorization) BatchGetSites( - ctx context.Context, - request *iamexamplev1.BatchGetSitesRequest, -) (*iamexamplev1.BatchGetSitesResponse, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().BatchGetSites().GetPermission() - if request.Parent != "" { - if ok, err := a.test(ctx, permission, request.Parent); err != nil { - return nil, err - } else if ok { - return a.Next.BatchGetSites(ctx, request) - } - } - if err := a.requireAll(ctx, permission, request.Names); err != nil { - return nil, err - } - return a.Next.BatchGetSites(ctx, request) -} - -func (a *Authorization) SearchSites( - ctx context.Context, - request *iamexamplev1.SearchSitesRequest, -) (*iamexamplev1.SearchSitesResponse, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().SearchSites().GetPermission() - if request.Parent != "" { - if ok, err := a.test(ctx, permission, request.Parent); err != nil { - return nil, err - } else if ok { - return a.Next.SearchSites(ctx, request) - } - } - response, err := a.Next.SearchSites(ctx, request) - if err != nil { - return nil, err - } - names := make([]string, 0, len(response.Sites)) - for _, site := range response.Sites { - names = append(names, site.Name) - } - if err := a.requireAll(ctx, permission, names); err != nil { - return nil, err - } - return response, nil -} - -func (a *Authorization) GetShipment( - ctx context.Context, - request *iamexamplev1.GetShipmentRequest, -) (*iamexamplev1.Shipment, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().GetShipment().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.GetShipment(ctx, request) -} - -func (a *Authorization) ListShipments( - ctx context.Context, - request *iamexamplev1.ListShipmentsRequest, -) (*iamexamplev1.ListShipmentsResponse, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().ListShipments().GetPermission() - if err := a.require(ctx, permission, request.GetParent()); err != nil { - return nil, err - } - return a.Next.ListShipments(ctx, request) -} - -func (a *Authorization) CreateShipment( - ctx context.Context, - request *iamexamplev1.CreateShipmentRequest, -) (*iamexamplev1.Shipment, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().CreateShipment().GetPermission() - if err := a.require(ctx, permission, request.GetParent()); err != nil { - return nil, err - } - return a.Next.CreateShipment(ctx, request) -} - func (a *Authorization) UpdateShipment( ctx context.Context, request *iamexamplev1.UpdateShipmentRequest, ) (*iamexamplev1.Shipment, error) { iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().UpdateShipment().GetPermission() + const permission = "freight.shipments.update" ok, err := a.test(ctx, permission, request.GetShipment().GetName()) if err != nil { return nil, err @@ -250,24 +45,12 @@ func (a *Authorization) UpdateShipment( return a.Next.UpdateShipment(ctx, request) } -func (a *Authorization) DeleteShipment( - ctx context.Context, - request *iamexamplev1.DeleteShipmentRequest, -) (*iamexamplev1.Shipment, error) { - iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().DeleteShipment().GetPermission() - if err := a.require(ctx, permission, request.GetName()); err != nil { - return nil, err - } - return a.Next.DeleteShipment(ctx, request) -} - func (a *Authorization) BatchGetShipments( ctx context.Context, request *iamexamplev1.BatchGetShipmentsRequest, ) (*iamexamplev1.BatchGetShipmentsResponse, error) { iamauthz.Authorize(ctx) - permission := iamexamplev1.FreightServiceIAM().BatchGetShipments().GetPermission() + const permission = "freight.shipments.get" if request.Parent != "" { if ok, err := a.test(ctx, permission, request.Parent); err != nil { return nil, err @@ -283,7 +66,7 @@ func (a *Authorization) BatchGetShipments( for _, shipment := range response.Shipments { resources = append(resources, shipment.Name, shipment.OriginSite, shipment.DestinationSite) } - results, err := a.IAM.TestPermissionOnResources(ctx, permission, resources) + results, err := a.IAMServer.TestPermissionOnResources(ctx, permission, resources) if err != nil { return nil, err } @@ -295,98 +78,25 @@ func (a *Authorization) BatchGetShipments( return response, nil } -func (a *Authorization) SetIamPolicy( - ctx context.Context, - request *iam.SetIamPolicyRequest, -) (*iam.Policy, error) { - iamauthz.Authorize(ctx) - var permission string - switch { - case request.Resource == iamresource.Root: - permission = "freight.root.setIamPolicy" - case resourcename.Match("shippers/{shipper}", request.Resource): - permission = "freight.shippers.setIamPolicy" - case resourcename.Match("shippers/{shipper}/sites/{site}", request.Resource): - permission = "freight.sites.setIamPolicy" - case resourcename.Match("shippers/{shipper}/shipments/{shipment}", request.Resource): - permission = "freight.shipments.setIamPolicy" - default: - return nil, status.Errorf(codes.InvalidArgument, "unsupported resource: %s", request.Resource) - } - if err := a.require(ctx, permission, request.Resource); err != nil { - return nil, err - } - return a.Next.SetIamPolicy(ctx, request) -} - -func (a *Authorization) GetIamPolicy( - ctx context.Context, - request *iam.GetIamPolicyRequest, -) (*iam.Policy, error) { - iamauthz.Authorize(ctx) - var permission string - switch { - case request.Resource == iamresource.Root: - permission = "freight.root.getIamPolicy" - case resourcename.Match("shippers/{shipper}", request.Resource): - permission = "freight.shippers.getIamPolicy" - case resourcename.Match("shippers/{shipper}/sites/{site}", request.Resource): - permission = "freight.sites.getIamPolicy" - case resourcename.Match("shippers/{shipper}/shipments/{shipment}", request.Resource): - permission = "freight.shipments.getIamPolicy" - default: - return nil, status.Errorf(codes.InvalidArgument, "unsupported resource: %s", request.Resource) - } - if err := a.require(ctx, permission, request.Resource); err != nil { - return nil, err - } - return a.Next.GetIamPolicy(ctx, request) -} - -func (a *Authorization) TestIamPermissions( - ctx context.Context, - request *iam.TestIamPermissionsRequest, -) (*iam.TestIamPermissionsResponse, error) { - iamauthz.Authorize(ctx) - return a.Next.TestIamPermissions(ctx, request) -} - -func (a *Authorization) require(ctx context.Context, permission, resource string) error { - if ok, err := a.test(ctx, permission, resource); err != nil { - return err - } else if !ok { - return status.Errorf(codes.PermissionDenied, "caller must have permission `%s`", permission) - } - return nil -} - func (a *Authorization) test(ctx context.Context, permission, resource string) (bool, error) { - return a.IAM.TestPermissionOnResource(ctx, permission, resource) + return a.IAMServer.TestPermissionOnResource(ctx, permission, resource) } -func (a *Authorization) testAll(ctx context.Context, permission string, resources []string) (bool, error) { - results, err := a.IAM.TestPermissionOnResources(ctx, permission, resources) +func (a *Authorization) testAny(ctx context.Context, permission string, resources []string) (bool, error) { + results, err := a.IAMServer.TestPermissionOnResources(ctx, permission, resources) if err != nil { return false, err } - all := true for _, resource := range resources { - all = all && results[resource] - } - return all, nil -} - -func (a *Authorization) requireAll(ctx context.Context, permission string, resources []string) error { - if ok, err := a.testAll(ctx, permission, resources); err != nil { - return err - } else if !ok { - return status.Errorf(codes.PermissionDenied, "caller must have permission `%s` on all resources", permission) + if results[resource] { + return true, nil + } } - return nil + return false, nil } func (a *Authorization) requireAny(ctx context.Context, permission string, resources []string) error { - if ok, err := a.testAll(ctx, permission, resources); err != nil { + if ok, err := a.testAny(ctx, permission, resources); err != nil { return err } else if !ok { return status.Errorf(codes.PermissionDenied, "caller must have permission `%s` on any of the resources", permission) diff --git a/iamexample/iamexampledata/roles.go b/iamexample/iamexampledata/roles.go deleted file mode 100644 index 70f46d29..00000000 --- a/iamexample/iamexampledata/roles.go +++ /dev/null @@ -1,24 +0,0 @@ -package iamexampledata - -import ( - iamexamplev1 "go.einride.tech/iam/proto/gen/einride/iam/example/v1" - iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" -) - -// PredefinedRoles returns the pre-defined roles for iamexamplev1.FreightServiceServer. -func PredefinedRoles() *iamv1.Roles { - desc, err := protoregistry.GlobalFiles.FindDescriptorByName( - protoreflect.FullName(iamexamplev1.FreightService_ServiceDesc.ServiceName), - ) - if err != nil { - return nil - } - serviceDesc, ok := desc.(protoreflect.ServiceDescriptor) - if !ok { - return nil - } - return proto.GetExtension(serviceDesc.Options(), iamv1.E_PredefinedRoles).(*iamv1.Roles) -} diff --git a/iamexample/server_test.go b/iamexample/server_test.go index a20cc094..c3c44b35 100644 --- a/iamexample/server_test.go +++ b/iamexample/server_test.go @@ -9,7 +9,6 @@ import ( "cloud.google.com/go/spanner" "go.einride.tech/aip/resourcename" "go.einride.tech/iam/iamauthz" - "go.einride.tech/iam/iamexample/iamexampledata" "go.einride.tech/iam/iamregistry" "go.einride.tech/iam/iamspanner" "go.einride.tech/iam/iamtest" @@ -50,13 +49,16 @@ func newServerTestSuite(t *testing.T) *serverTestSuite { } func (ts *serverTestSuite) newTestFixture(t *testing.T) *serverTestFixture { - roles, err := iamregistry.NewRoles(iamexampledata.PredefinedRoles()) + iamDescriptor, err := iamexamplev1.NewFreightServiceIAMDescriptor() + assert.NilError(t, err) + roles, err := iamregistry.NewRoles(iamDescriptor.PredefinedRoles) assert.NilError(t, err) spannerClient := ts.spanner.NewDatabaseFromDDLFiles(t, "schema.sql", "../iamspanner/schema.sql") + memberResolver := NewIAMMemberHeaderResolver() iamServer, err := iamspanner.NewIAMServer( spannerClient, roles, - NewIAMMemberHeaderResolver(), + memberResolver, iamspanner.ServerConfig{ ErrorHook: func(ctx context.Context, err error) { t.Log(err) @@ -73,9 +75,13 @@ func (ts *serverTestSuite) newTestFixture(t *testing.T) *serverTestFixture { }, }, } + authorization, err := iamexamplev1.NewFreightServiceAuthorization(server, iamServer, memberResolver) + assert.NilError(t, err) serverWithAuthorization := &Authorization{ - IAM: iamServer, - Next: server, + Next: server, + IAMServer: iamServer, + IAMDescriptor: iamDescriptor, + FreightServiceAuthorization: authorization, } lis, err := net.Listen("tcp", "localhost:0") assert.NilError(t, err) diff --git a/iamreflect/iamdescriptor.go b/iamreflect/iamdescriptor.go new file mode 100644 index 00000000..69728571 --- /dev/null +++ b/iamreflect/iamdescriptor.go @@ -0,0 +1,154 @@ +package iamreflect + +import ( + "fmt" + + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" + "google.golang.org/genproto/googleapis/api/annotations" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" +) + +// IAMDescriptor represents an RPC service's IAM specification. +type IAMDescriptor struct { + // PredefinedRoles are the service's predefined IAM roles. + PredefinedRoles *iamv1.Roles + // MethodAuthorizationOptions is a mapping from full method name to the method's authorization options. + MethodAuthorizationOptions map[protoreflect.FullName]*iamv1.MethodAuthorizationOptions + // RequestAuthorizationOptions is a mapping from full request name to the method's authorization options. + RequestAuthorizationOptions map[protoreflect.FullName]*iamv1.MethodAuthorizationOptions +} + +// NewIAMDescriptor creates a new IAMDescriptor from the provided service descriptor. +// Uses the provided files to resolve resource name patterns. +func NewIAMDescriptor(service protoreflect.ServiceDescriptor, files *protoregistry.Files) (*IAMDescriptor, error) { + result := IAMDescriptor{ + MethodAuthorizationOptions: make( + map[protoreflect.FullName]*iamv1.MethodAuthorizationOptions, service.Methods().Len(), + ), + RequestAuthorizationOptions: make( + map[protoreflect.FullName]*iamv1.MethodAuthorizationOptions, service.Methods().Len(), + ), + } + if predefinedRoles := proto.GetExtension( + service.Options(), iamv1.E_PredefinedRoles, + ).(*iamv1.Roles); predefinedRoles != nil { + result.PredefinedRoles = proto.Clone(predefinedRoles).(*iamv1.Roles) + } + for i := 0; i < service.Methods().Len(); i++ { + method := service.Methods().Get(i) + if methodAuthorizationOptions := proto.GetExtension( + method.Options(), iamv1.E_MethodAuthorization, + ).(*iamv1.MethodAuthorizationOptions); methodAuthorizationOptions != nil { + methodAuthorizationOptions = proto.Clone(methodAuthorizationOptions).(*iamv1.MethodAuthorizationOptions) + result.MethodAuthorizationOptions[method.FullName()] = methodAuthorizationOptions + if _, ok := result.RequestAuthorizationOptions[method.Input().FullName()]; ok { + return nil, fmt.Errorf( + "new %s IAM descriptor: service uses the same request %s for multiple methods", + service.Name(), + method.Input().FullName(), + ) + } + result.RequestAuthorizationOptions[method.Input().FullName()] = methodAuthorizationOptions + } + } + for _, methodAuthorizationOptions := range result.MethodAuthorizationOptions { + resourcePermissions := methodAuthorizationOptions.GetResourcePermissions() + if resourcePermissions == nil { + continue + } + for _, resourcePermission := range resourcePermissions.ResourcePermission { + if len(resourcePermission.Resource.GetPattern()) > 0 { + // Resource is annotated with patterns manually. No need to resolve. + continue + } + resource, ok := resolveResource(resourcePermission.Resource.GetType(), service, files) + if !ok { + return nil, fmt.Errorf( + "new %s IAM descriptor: unable to resolve resource '%s' patterns", + service.Name(), + resource.GetType(), + ) + } + if len(resource.Pattern) == 0 { + return nil, fmt.Errorf( + "new %s IAM descriptor: resource '%s' has no patterns", + service.Name(), + resource.GetType(), + ) + } + resourcePermission.Resource = proto.Clone(resource).(*annotations.ResourceDescriptor) + } + } + return &result, nil +} + +func (d *IAMDescriptor) ResolvePermissionByRequestAndResource( + request proto.Message, + resource string, +) (string, bool) { + methodAuthorizationOptions, ok := d.FindMethodAuthorizationOptionsByRequest(request) + if !ok { + return "", false + } + switch permissions := methodAuthorizationOptions.Permissions.(type) { + case *iamv1.MethodAuthorizationOptions_Permission: + return permissions.Permission, true + case *iamv1.MethodAuthorizationOptions_ResourcePermissions: + return ResolveResourcePermission(permissions.ResourcePermissions, resource) + default: + return "", false + } +} + +func (d *IAMDescriptor) FindMethodAuthorizationOptionsByRequest( + request proto.Message, +) (*iamv1.MethodAuthorizationOptions, bool) { + result, ok := d.RequestAuthorizationOptions[request.ProtoReflect().Descriptor().FullName()] + return result, ok +} + +func resolveResource( + resourceType string, + service protoreflect.ServiceDescriptor, + files *protoregistry.Files, +) (*annotations.ResourceDescriptor, bool) { + var result *annotations.ResourceDescriptor + var searchMessagesFn func(protoreflect.MessageDescriptors) bool + searchMessagesFn = func(messages protoreflect.MessageDescriptors) bool { + for i := 0; i < messages.Len(); i++ { + message := messages.Get(i) + if resource := proto.GetExtension( + message.Options(), annotations.E_Resource, + ).(*annotations.ResourceDescriptor); resource != nil { + result = resource + return false + } + if !searchMessagesFn(message.Messages()) { + return false + } + } + return true + } + searchFileFn := func(file protoreflect.FileDescriptor) bool { + // Search file annotations. + for _, resource := range proto.GetExtension( + file.Options(), annotations.E_ResourceDefinition, + ).([]*annotations.ResourceDescriptor) { + if resource.Type == resourceType { + result = resource + return false + } + } + return searchMessagesFn(file.Messages()) + } + // Start with a narrow search in the same package. + files.RangeFilesByPackage(service.ParentFile().Package(), searchFileFn) + if result != nil { + return result, true + } + // Fall back to a broad search of all files. + files.RangeFiles(searchFileFn) + return result, result != nil +} diff --git a/iamreflect/resourcepermission.go b/iamreflect/resourcepermission.go new file mode 100644 index 00000000..41449c49 --- /dev/null +++ b/iamreflect/resourcepermission.go @@ -0,0 +1,22 @@ +package iamreflect + +import ( + "go.einride.tech/aip/resourcename" + "go.einride.tech/iam/iamresource" + iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1" +) + +// ResolveResourcePermission resolves a permission for a resource name, given a set of resource permissions. +func ResolveResourcePermission(resourcePermissions *iamv1.ResourcePermissions, resourceName string) (string, bool) { + for _, resourcePermission := range resourcePermissions.GetResourcePermission() { + if resourcePermission.GetResource().GetType() == iamresource.Root && resourceName == iamresource.Root { + return resourcePermission.GetPermission(), true + } + for _, pattern := range resourcePermission.GetResource().GetPattern() { + if resourcename.Match(pattern, resourceName) { + return resourcePermission.GetPermission(), true + } + } + } + return "", false +} diff --git a/iamspanner/server.go b/iamspanner/server.go index fe76459c..307431dd 100644 --- a/iamspanner/server.go +++ b/iamspanner/server.go @@ -157,6 +157,57 @@ func (s *IAMServer) TestPermissionOnResource( return result[resource], nil } +func (s *IAMServer) TestResourcePermission( + ctx context.Context, + members []string, + resource string, + permission string, +) (bool, error) { + result, err := s.TestResourcePermissions(ctx, members, map[string]string{resource: permission}) + if err != nil { + return false, err + } + return result[resource], nil +} + +func (s *IAMServer) TestResourcePermissions( + ctx context.Context, + members []string, + resourcePermissions map[string]string, +) (map[string]bool, error) { + result := make(map[string]bool, len(resourcePermissions)) + tx := s.client.Single() + defer tx.Close() + resources := make([]string, 0, len(resourcePermissions)) + for resource := range resourcePermissions { + resources = append(resources, resource) + } + if err := s.ReadRolesBoundToMembersAndResourcesInTransaction( + ctx, + tx, + members, + resources, + func(ctx context.Context, _ string, boundResource string, role *admin.Role) error { + for resource, permission := range resourcePermissions { + result[resource] = result[resource] || + (boundResource == iamresource.Root || + resource == boundResource || + resourcename.HasParent(resource, boundResource) && + iamrole.HasPermission(role, permission)) + } + return nil + }, + ); err != nil { + return nil, s.handleStorageError(ctx, err) + } + for resource := range resourcePermissions { + if _, ok := result[resource]; !ok { + result[resource] = false + } + } + return result, nil +} + // TestPermissionOnResources tests if the caller has the specified permission on the specified resources. func (s *IAMServer) TestPermissionOnResources( ctx context.Context, diff --git a/proto/gen/einride/iam/example/v1/freight_service.pb.go b/proto/gen/einride/iam/example/v1/freight_service.pb.go index 993a9172..a04f4ef5 100644 --- a/proto/gen/einride/iam/example/v1/freight_service.pb.go +++ b/proto/gen/einride/iam/example/v1/freight_service.pb.go @@ -1671,7 +1671,7 @@ var file_einride_iam_example_v1_freight_service_proto_rawDesc = []byte{ 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, - 0x73, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x32, 0xfb, 0x3f, 0x0a, 0x0e, 0x46, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x32, 0xeb, 0x3f, 0x0a, 0x0e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xec, 0x01, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, 0x12, 0x29, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, @@ -1833,21 +1833,20 @@ var file_einride_iam_example_v1_freight_service_proto_rawDesc = []byte{ 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x2a, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x73, 0x68, 0x69, 0x70, 0x70, 0x65, 0x72, - 0x73, 0x2f, 0x2a, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x2a, 0x7d, 0x12, 0x9d, 0x03, 0x0a, + 0x73, 0x2f, 0x2a, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x2a, 0x7d, 0x12, 0x8d, 0x03, 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xae, 0x02, 0x82, 0xb8, - 0x62, 0xfb, 0x01, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x73, 0x69, 0x74, - 0x65, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x22, 0xe5, 0x01, 0x0a, 0x4f, 0x74, 0x65, 0x73, 0x74, 0x28, + 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9e, 0x02, 0x82, 0xb8, + 0x62, 0xeb, 0x01, 0x0a, 0x11, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x73, 0x69, 0x74, + 0x65, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x1a, 0xd5, 0x01, 0x0a, 0x3f, 0x74, 0x65, 0x73, 0x74, 0x28, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x65, 0x73, 0x74, 0x5f, - 0x61, 0x6c, 0x6c, 0x28, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x6d, 0x61, 0x70, 0x28, 0x73, - 0x2c, 0x20, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x29, 0x29, 0x1a, 0x91, 0x01, 0x54, 0x68, 0x65, + 0x61, 0x6c, 0x6c, 0x28, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x29, 0x1a, 0x91, 0x01, 0x54, 0x68, 0x65, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x67, 0x65, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x69, 0x74, 0x65, 0x73, 0x20, 0x75, 0x6e, diff --git a/proto/gen/einride/iam/example/v1/freight_service_iam.go b/proto/gen/einride/iam/example/v1/freight_service_iam.go deleted file mode 100644 index d571a5aa..00000000 --- a/proto/gen/einride/iam/example/v1/freight_service_iam.go +++ /dev/null @@ -1,405 +0,0 @@ -package iamexamplev1 - -import ( - v1 "go.einride.tech/iam/proto/gen/einride/iam/v1" - proto "google.golang.org/protobuf/proto" - protoregistry "google.golang.org/protobuf/reflect/protoregistry" -) - -// FreightServiceIAM returns a descriptor for the FreightService IAM configuration. -func FreightServiceIAM() FreightServiceIAMDescriptor { - return &file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService -} - -// FreightServiceIAMDescriptor describes the FreightService IAM configuration. -type FreightServiceIAMDescriptor interface { - PredefinedRoles() *v1.Roles - GetShipper() *v1.MethodAuthorizationOptions - ListShippers() *v1.MethodAuthorizationOptions - CreateShipper() *v1.MethodAuthorizationOptions - UpdateShipper() *v1.MethodAuthorizationOptions - DeleteShipper() *v1.MethodAuthorizationOptions - GetSite() *v1.MethodAuthorizationOptions - ListSites() *v1.MethodAuthorizationOptions - CreateSite() *v1.MethodAuthorizationOptions - UpdateSite() *v1.MethodAuthorizationOptions - DeleteSite() *v1.MethodAuthorizationOptions - BatchGetSites() *v1.MethodAuthorizationOptions - SearchSites() *v1.MethodAuthorizationOptions - GetShipment() *v1.MethodAuthorizationOptions - ListShipments() *v1.MethodAuthorizationOptions - CreateShipment() *v1.MethodAuthorizationOptions - UpdateShipment() *v1.MethodAuthorizationOptions - DeleteShipment() *v1.MethodAuthorizationOptions - BatchGetShipments() *v1.MethodAuthorizationOptions - SetIamPolicy() *v1.MethodAuthorizationOptions - GetIamPolicy() *v1.MethodAuthorizationOptions - TestIamPermissions() *v1.MethodAuthorizationOptions -} - -type freightServiceIAMDescriptor struct { - predefinedRoles *v1.Roles - getShipper *v1.MethodAuthorizationOptions - listShippers *v1.MethodAuthorizationOptions - createShipper *v1.MethodAuthorizationOptions - updateShipper *v1.MethodAuthorizationOptions - deleteShipper *v1.MethodAuthorizationOptions - getSite *v1.MethodAuthorizationOptions - listSites *v1.MethodAuthorizationOptions - createSite *v1.MethodAuthorizationOptions - updateSite *v1.MethodAuthorizationOptions - deleteSite *v1.MethodAuthorizationOptions - batchGetSites *v1.MethodAuthorizationOptions - searchSites *v1.MethodAuthorizationOptions - getShipment *v1.MethodAuthorizationOptions - listShipments *v1.MethodAuthorizationOptions - createShipment *v1.MethodAuthorizationOptions - updateShipment *v1.MethodAuthorizationOptions - deleteShipment *v1.MethodAuthorizationOptions - batchGetShipments *v1.MethodAuthorizationOptions - setIamPolicy *v1.MethodAuthorizationOptions - getIamPolicy *v1.MethodAuthorizationOptions - testIamPermissions *v1.MethodAuthorizationOptions -} - -func (d *freightServiceIAMDescriptor) PredefinedRoles() *v1.Roles { - return d.predefinedRoles -} - -func (d *freightServiceIAMDescriptor) GetShipper() *v1.MethodAuthorizationOptions { - return d.getShipper -} - -func (d *freightServiceIAMDescriptor) ListShippers() *v1.MethodAuthorizationOptions { - return d.listShippers -} - -func (d *freightServiceIAMDescriptor) CreateShipper() *v1.MethodAuthorizationOptions { - return d.createShipper -} - -func (d *freightServiceIAMDescriptor) UpdateShipper() *v1.MethodAuthorizationOptions { - return d.updateShipper -} - -func (d *freightServiceIAMDescriptor) DeleteShipper() *v1.MethodAuthorizationOptions { - return d.deleteShipper -} - -func (d *freightServiceIAMDescriptor) GetSite() *v1.MethodAuthorizationOptions { - return d.getSite -} - -func (d *freightServiceIAMDescriptor) ListSites() *v1.MethodAuthorizationOptions { - return d.listSites -} - -func (d *freightServiceIAMDescriptor) CreateSite() *v1.MethodAuthorizationOptions { - return d.createSite -} - -func (d *freightServiceIAMDescriptor) UpdateSite() *v1.MethodAuthorizationOptions { - return d.updateSite -} - -func (d *freightServiceIAMDescriptor) DeleteSite() *v1.MethodAuthorizationOptions { - return d.deleteSite -} - -func (d *freightServiceIAMDescriptor) BatchGetSites() *v1.MethodAuthorizationOptions { - return d.batchGetSites -} - -func (d *freightServiceIAMDescriptor) SearchSites() *v1.MethodAuthorizationOptions { - return d.searchSites -} - -func (d *freightServiceIAMDescriptor) GetShipment() *v1.MethodAuthorizationOptions { - return d.getShipment -} - -func (d *freightServiceIAMDescriptor) ListShipments() *v1.MethodAuthorizationOptions { - return d.listShipments -} - -func (d *freightServiceIAMDescriptor) CreateShipment() *v1.MethodAuthorizationOptions { - return d.createShipment -} - -func (d *freightServiceIAMDescriptor) UpdateShipment() *v1.MethodAuthorizationOptions { - return d.updateShipment -} - -func (d *freightServiceIAMDescriptor) DeleteShipment() *v1.MethodAuthorizationOptions { - return d.deleteShipment -} - -func (d *freightServiceIAMDescriptor) BatchGetShipments() *v1.MethodAuthorizationOptions { - return d.batchGetShipments -} - -func (d *freightServiceIAMDescriptor) SetIamPolicy() *v1.MethodAuthorizationOptions { - return d.setIamPolicy -} - -func (d *freightServiceIAMDescriptor) GetIamPolicy() *v1.MethodAuthorizationOptions { - return d.getIamPolicy -} - -func (d *freightServiceIAMDescriptor) TestIamPermissions() *v1.MethodAuthorizationOptions { - return d.testIamPermissions -} - -var file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService freightServiceIAMDescriptor - -func file_einride_iam_example_v1_freight_service_proto_init_iamDescriptor_FreightService() { - // Init predefined roles. - serviceDesc, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService", - ) - if err != nil { - panic("unable to find service descriptor einride.iam.example.v1.FreightService") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.predefinedRoles = proto.GetExtension( - serviceDesc.Options(), - v1.E_PredefinedRoles, - ).(*v1.Roles) - // Init GetShipper authorization options. - methodDescGetShipper, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.GetShipper", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.GetShipper") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.getShipper = proto.GetExtension( - methodDescGetShipper.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init ListShippers authorization options. - methodDescListShippers, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.ListShippers", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.ListShippers") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.listShippers = proto.GetExtension( - methodDescListShippers.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init CreateShipper authorization options. - methodDescCreateShipper, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.CreateShipper", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.CreateShipper") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.createShipper = proto.GetExtension( - methodDescCreateShipper.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init UpdateShipper authorization options. - methodDescUpdateShipper, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.UpdateShipper", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.UpdateShipper") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.updateShipper = proto.GetExtension( - methodDescUpdateShipper.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init DeleteShipper authorization options. - methodDescDeleteShipper, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.DeleteShipper", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.DeleteShipper") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.deleteShipper = proto.GetExtension( - methodDescDeleteShipper.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init GetSite authorization options. - methodDescGetSite, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.GetSite", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.GetSite") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.getSite = proto.GetExtension( - methodDescGetSite.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init ListSites authorization options. - methodDescListSites, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.ListSites", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.ListSites") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.listSites = proto.GetExtension( - methodDescListSites.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init CreateSite authorization options. - methodDescCreateSite, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.CreateSite", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.CreateSite") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.createSite = proto.GetExtension( - methodDescCreateSite.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init UpdateSite authorization options. - methodDescUpdateSite, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.UpdateSite", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.UpdateSite") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.updateSite = proto.GetExtension( - methodDescUpdateSite.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init DeleteSite authorization options. - methodDescDeleteSite, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.DeleteSite", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.DeleteSite") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.deleteSite = proto.GetExtension( - methodDescDeleteSite.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init BatchGetSites authorization options. - methodDescBatchGetSites, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.BatchGetSites", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.BatchGetSites") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.batchGetSites = proto.GetExtension( - methodDescBatchGetSites.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init SearchSites authorization options. - methodDescSearchSites, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.SearchSites", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.SearchSites") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.searchSites = proto.GetExtension( - methodDescSearchSites.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init GetShipment authorization options. - methodDescGetShipment, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.GetShipment", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.GetShipment") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.getShipment = proto.GetExtension( - methodDescGetShipment.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init ListShipments authorization options. - methodDescListShipments, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.ListShipments", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.ListShipments") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.listShipments = proto.GetExtension( - methodDescListShipments.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init CreateShipment authorization options. - methodDescCreateShipment, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.CreateShipment", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.CreateShipment") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.createShipment = proto.GetExtension( - methodDescCreateShipment.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init UpdateShipment authorization options. - methodDescUpdateShipment, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.UpdateShipment", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.UpdateShipment") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.updateShipment = proto.GetExtension( - methodDescUpdateShipment.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init DeleteShipment authorization options. - methodDescDeleteShipment, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.DeleteShipment", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.DeleteShipment") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.deleteShipment = proto.GetExtension( - methodDescDeleteShipment.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init BatchGetShipments authorization options. - methodDescBatchGetShipments, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.BatchGetShipments", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.BatchGetShipments") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.batchGetShipments = proto.GetExtension( - methodDescBatchGetShipments.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init SetIamPolicy authorization options. - methodDescSetIamPolicy, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.SetIamPolicy", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.SetIamPolicy") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.setIamPolicy = proto.GetExtension( - methodDescSetIamPolicy.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init GetIamPolicy authorization options. - methodDescGetIamPolicy, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.GetIamPolicy", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.GetIamPolicy") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.getIamPolicy = proto.GetExtension( - methodDescGetIamPolicy.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) - // Init TestIamPermissions authorization options. - methodDescTestIamPermissions, err := protoregistry.GlobalFiles.FindDescriptorByName( - "einride.iam.example.v1.FreightService.TestIamPermissions", - ) - if err != nil { - panic("unable to find method descriptor einride.iam.example.v1.FreightService.TestIamPermissions") - } - file_einride_iam_example_v1_freight_service_proto_iamDescriptor_FreightService.testIamPermissions = proto.GetExtension( - methodDescTestIamPermissions.Options(), - v1.E_MethodAuthorization, - ).(*v1.MethodAuthorizationOptions) -} - -func init() { - // This init function runs after the init function of go.einride.tech/iam/proto/gen/einride/iam/example/v1/freight_service.pb.go. - // We depend on the Go initialization order to ensure this. - // See: https://golang.org/ref/spec#Package_initialization - file_einride_iam_example_v1_freight_service_proto_init_iamDescriptor_FreightService() -} diff --git a/proto/gen/einride/iam/example/v1/freight_service_iam.pb.go b/proto/gen/einride/iam/example/v1/freight_service_iam.pb.go new file mode 100644 index 00000000..f3e8ca08 --- /dev/null +++ b/proto/gen/einride/iam/example/v1/freight_service_iam.pb.go @@ -0,0 +1,539 @@ +// Code generated by protoc-gen-go-iam. DO NOT EDIT. +// versions: +// protoc v3.15.2 + +package iamexamplev1 + +import ( + context "context" + fmt "fmt" + iamauthz "go.einride.tech/iam/iamauthz" + iammember "go.einride.tech/iam/iammember" + iamreflect "go.einride.tech/iam/iamreflect" + v1 "google.golang.org/genproto/googleapis/iam/v1" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoregistry "google.golang.org/protobuf/reflect/protoregistry" +) + +// NewFreightServiceIAMDescriptor returns a new FreightService IAM descriptor. +func NewFreightServiceIAMDescriptor() (*iamreflect.IAMDescriptor, error) { + descriptor, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService") + if err != nil { + return nil, fmt.Errorf("new FreightService IAM descriptor: %w", err) + } + service, ok := descriptor.(protoreflect.ServiceDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightServiceIAM descriptor: got non-service descriptor") + } + return iamreflect.NewIAMDescriptor(service, protoregistry.GlobalFiles) +} + +// NewFreightServiceAuthorization creates a new authorization middleware for FreightService. +func NewFreightServiceAuthorization( + next FreightServiceServer, + permissionTester iamauthz.PermissionTester, + memberResolver iammember.Resolver, +) (*FreightServiceAuthorization, error) { + var result FreightServiceAuthorization + result.next = next + descriptorGetShipper, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.GetShipper") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for GetShipper") + } + methodGetShipper, ok := descriptorGetShipper.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for GetShipper") + } + beforeGetShipper, err := iamauthz.NewBeforeMethodAuthorization(methodGetShipper, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeGetShipper = beforeGetShipper + descriptorListShippers, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.ListShippers") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for ListShippers") + } + methodListShippers, ok := descriptorListShippers.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for ListShippers") + } + beforeListShippers, err := iamauthz.NewBeforeMethodAuthorization(methodListShippers, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeListShippers = beforeListShippers + descriptorCreateShipper, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.CreateShipper") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for CreateShipper") + } + methodCreateShipper, ok := descriptorCreateShipper.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for CreateShipper") + } + beforeCreateShipper, err := iamauthz.NewBeforeMethodAuthorization(methodCreateShipper, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeCreateShipper = beforeCreateShipper + descriptorUpdateShipper, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.UpdateShipper") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for UpdateShipper") + } + methodUpdateShipper, ok := descriptorUpdateShipper.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for UpdateShipper") + } + beforeUpdateShipper, err := iamauthz.NewBeforeMethodAuthorization(methodUpdateShipper, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeUpdateShipper = beforeUpdateShipper + descriptorDeleteShipper, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.DeleteShipper") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for DeleteShipper") + } + methodDeleteShipper, ok := descriptorDeleteShipper.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for DeleteShipper") + } + beforeDeleteShipper, err := iamauthz.NewBeforeMethodAuthorization(methodDeleteShipper, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeDeleteShipper = beforeDeleteShipper + descriptorGetSite, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.GetSite") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for GetSite") + } + methodGetSite, ok := descriptorGetSite.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for GetSite") + } + beforeGetSite, err := iamauthz.NewBeforeMethodAuthorization(methodGetSite, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeGetSite = beforeGetSite + descriptorListSites, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.ListSites") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for ListSites") + } + methodListSites, ok := descriptorListSites.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for ListSites") + } + beforeListSites, err := iamauthz.NewBeforeMethodAuthorization(methodListSites, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeListSites = beforeListSites + descriptorCreateSite, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.CreateSite") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for CreateSite") + } + methodCreateSite, ok := descriptorCreateSite.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for CreateSite") + } + beforeCreateSite, err := iamauthz.NewBeforeMethodAuthorization(methodCreateSite, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeCreateSite = beforeCreateSite + descriptorUpdateSite, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.UpdateSite") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for UpdateSite") + } + methodUpdateSite, ok := descriptorUpdateSite.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for UpdateSite") + } + beforeUpdateSite, err := iamauthz.NewBeforeMethodAuthorization(methodUpdateSite, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeUpdateSite = beforeUpdateSite + descriptorDeleteSite, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.DeleteSite") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for DeleteSite") + } + methodDeleteSite, ok := descriptorDeleteSite.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for DeleteSite") + } + beforeDeleteSite, err := iamauthz.NewBeforeMethodAuthorization(methodDeleteSite, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeDeleteSite = beforeDeleteSite + descriptorBatchGetSites, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.BatchGetSites") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for BatchGetSites") + } + methodBatchGetSites, ok := descriptorBatchGetSites.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for BatchGetSites") + } + beforeBatchGetSites, err := iamauthz.NewBeforeMethodAuthorization(methodBatchGetSites, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeBatchGetSites = beforeBatchGetSites + descriptorSearchSites, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.SearchSites") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for SearchSites") + } + methodSearchSites, ok := descriptorSearchSites.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for SearchSites") + } + afterSearchSites, err := iamauthz.NewAfterMethodAuthorization(methodSearchSites, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.afterSearchSites = afterSearchSites + descriptorGetShipment, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.GetShipment") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for GetShipment") + } + methodGetShipment, ok := descriptorGetShipment.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for GetShipment") + } + afterGetShipment, err := iamauthz.NewAfterMethodAuthorization(methodGetShipment, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.afterGetShipment = afterGetShipment + descriptorListShipments, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.ListShipments") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for ListShipments") + } + methodListShipments, ok := descriptorListShipments.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for ListShipments") + } + beforeListShipments, err := iamauthz.NewBeforeMethodAuthorization(methodListShipments, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeListShipments = beforeListShipments + descriptorCreateShipment, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.CreateShipment") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for CreateShipment") + } + methodCreateShipment, ok := descriptorCreateShipment.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for CreateShipment") + } + beforeCreateShipment, err := iamauthz.NewBeforeMethodAuthorization(methodCreateShipment, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeCreateShipment = beforeCreateShipment + descriptorDeleteShipment, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.DeleteShipment") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for DeleteShipment") + } + methodDeleteShipment, ok := descriptorDeleteShipment.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for DeleteShipment") + } + beforeDeleteShipment, err := iamauthz.NewBeforeMethodAuthorization(methodDeleteShipment, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeDeleteShipment = beforeDeleteShipment + descriptorBatchGetShipments, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.BatchGetShipments") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for BatchGetShipments") + } + methodBatchGetShipments, ok := descriptorBatchGetShipments.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for BatchGetShipments") + } + afterBatchGetShipments, err := iamauthz.NewAfterMethodAuthorization(methodBatchGetShipments, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.afterBatchGetShipments = afterBatchGetShipments + descriptorSetIamPolicy, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.SetIamPolicy") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for SetIamPolicy") + } + methodSetIamPolicy, ok := descriptorSetIamPolicy.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for SetIamPolicy") + } + beforeSetIamPolicy, err := iamauthz.NewBeforeMethodAuthorization(methodSetIamPolicy, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeSetIamPolicy = beforeSetIamPolicy + descriptorGetIamPolicy, err := protoregistry.GlobalFiles.FindDescriptorByName("einride.iam.example.v1.FreightService.GetIamPolicy") + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: failed to find descriptor for GetIamPolicy") + } + methodGetIamPolicy, ok := descriptorGetIamPolicy.(protoreflect.MethodDescriptor) + if !ok { + return nil, fmt.Errorf("new FreightService authorization: got non-method descriptor for GetIamPolicy") + } + beforeGetIamPolicy, err := iamauthz.NewBeforeMethodAuthorization(methodGetIamPolicy, permissionTester, memberResolver) + if err != nil { + return nil, fmt.Errorf("new FreightService authorization: %w", err) + } + result.beforeGetIamPolicy = beforeGetIamPolicy + return &result, nil +} + +type FreightServiceAuthorization struct { + next FreightServiceServer + beforeGetShipper *iamauthz.BeforeMethodAuthorization + beforeListShippers *iamauthz.BeforeMethodAuthorization + beforeCreateShipper *iamauthz.BeforeMethodAuthorization + beforeUpdateShipper *iamauthz.BeforeMethodAuthorization + beforeDeleteShipper *iamauthz.BeforeMethodAuthorization + beforeGetSite *iamauthz.BeforeMethodAuthorization + beforeListSites *iamauthz.BeforeMethodAuthorization + beforeCreateSite *iamauthz.BeforeMethodAuthorization + beforeUpdateSite *iamauthz.BeforeMethodAuthorization + beforeDeleteSite *iamauthz.BeforeMethodAuthorization + beforeBatchGetSites *iamauthz.BeforeMethodAuthorization + afterSearchSites *iamauthz.AfterMethodAuthorization + afterGetShipment *iamauthz.AfterMethodAuthorization + beforeListShipments *iamauthz.BeforeMethodAuthorization + beforeCreateShipment *iamauthz.BeforeMethodAuthorization + beforeDeleteShipment *iamauthz.BeforeMethodAuthorization + afterBatchGetShipments *iamauthz.AfterMethodAuthorization + beforeSetIamPolicy *iamauthz.BeforeMethodAuthorization + beforeGetIamPolicy *iamauthz.BeforeMethodAuthorization +} + +func (a *FreightServiceAuthorization) GetShipper( + ctx context.Context, + request *GetShipperRequest, +) (*Shipper, error) { + ctx, err := a.beforeGetShipper.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.GetShipper(ctx, request) +} + +func (a *FreightServiceAuthorization) ListShippers( + ctx context.Context, + request *ListShippersRequest, +) (*ListShippersResponse, error) { + ctx, err := a.beforeListShippers.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.ListShippers(ctx, request) +} + +func (a *FreightServiceAuthorization) CreateShipper( + ctx context.Context, + request *CreateShipperRequest, +) (*Shipper, error) { + ctx, err := a.beforeCreateShipper.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.CreateShipper(ctx, request) +} + +func (a *FreightServiceAuthorization) UpdateShipper( + ctx context.Context, + request *UpdateShipperRequest, +) (*Shipper, error) { + ctx, err := a.beforeUpdateShipper.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.UpdateShipper(ctx, request) +} + +func (a *FreightServiceAuthorization) DeleteShipper( + ctx context.Context, + request *DeleteShipperRequest, +) (*Shipper, error) { + ctx, err := a.beforeDeleteShipper.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.DeleteShipper(ctx, request) +} + +func (a *FreightServiceAuthorization) GetSite( + ctx context.Context, + request *GetSiteRequest, +) (*Site, error) { + ctx, err := a.beforeGetSite.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.GetSite(ctx, request) +} + +func (a *FreightServiceAuthorization) ListSites( + ctx context.Context, + request *ListSitesRequest, +) (*ListSitesResponse, error) { + ctx, err := a.beforeListSites.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.ListSites(ctx, request) +} + +func (a *FreightServiceAuthorization) CreateSite( + ctx context.Context, + request *CreateSiteRequest, +) (*Site, error) { + ctx, err := a.beforeCreateSite.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.CreateSite(ctx, request) +} + +func (a *FreightServiceAuthorization) UpdateSite( + ctx context.Context, + request *UpdateSiteRequest, +) (*Site, error) { + ctx, err := a.beforeUpdateSite.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.UpdateSite(ctx, request) +} + +func (a *FreightServiceAuthorization) DeleteSite( + ctx context.Context, + request *DeleteSiteRequest, +) (*Site, error) { + ctx, err := a.beforeDeleteSite.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.DeleteSite(ctx, request) +} + +func (a *FreightServiceAuthorization) BatchGetSites( + ctx context.Context, + request *BatchGetSitesRequest, +) (*BatchGetSitesResponse, error) { + ctx, err := a.beforeBatchGetSites.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.BatchGetSites(ctx, request) +} + +func (a *FreightServiceAuthorization) SearchSites( + ctx context.Context, + request *SearchSitesRequest, +) (*SearchSitesResponse, error) { + response, err := a.next.SearchSites(ctx, request) + _, errAuth := a.afterSearchSites.AuthorizeRequestAndResponse(ctx, request, response) + if errAuth != nil { + return nil, errAuth + } + return response, err +} + +func (a *FreightServiceAuthorization) GetShipment( + ctx context.Context, + request *GetShipmentRequest, +) (*Shipment, error) { + response, err := a.next.GetShipment(ctx, request) + _, errAuth := a.afterGetShipment.AuthorizeRequestAndResponse(ctx, request, response) + if errAuth != nil { + return nil, errAuth + } + return response, err +} + +func (a *FreightServiceAuthorization) ListShipments( + ctx context.Context, + request *ListShipmentsRequest, +) (*ListShipmentsResponse, error) { + ctx, err := a.beforeListShipments.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.ListShipments(ctx, request) +} + +func (a *FreightServiceAuthorization) CreateShipment( + ctx context.Context, + request *CreateShipmentRequest, +) (*Shipment, error) { + ctx, err := a.beforeCreateShipment.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.CreateShipment(ctx, request) +} + +func (a *FreightServiceAuthorization) UpdateShipment( + ctx context.Context, + request *UpdateShipmentRequest, +) (*Shipment, error) { + return nil, status.Error(codes.Internal, "authorization not configured") +} + +func (a *FreightServiceAuthorization) DeleteShipment( + ctx context.Context, + request *DeleteShipmentRequest, +) (*Shipment, error) { + ctx, err := a.beforeDeleteShipment.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.DeleteShipment(ctx, request) +} + +func (a *FreightServiceAuthorization) BatchGetShipments( + ctx context.Context, + request *BatchGetShipmentsRequest, +) (*BatchGetShipmentsResponse, error) { + response, err := a.next.BatchGetShipments(ctx, request) + _, errAuth := a.afterBatchGetShipments.AuthorizeRequestAndResponse(ctx, request, response) + if errAuth != nil { + return nil, errAuth + } + return response, err +} + +func (a *FreightServiceAuthorization) SetIamPolicy( + ctx context.Context, + request *v1.SetIamPolicyRequest, +) (*v1.Policy, error) { + ctx, err := a.beforeSetIamPolicy.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.SetIamPolicy(ctx, request) +} + +func (a *FreightServiceAuthorization) GetIamPolicy( + ctx context.Context, + request *v1.GetIamPolicyRequest, +) (*v1.Policy, error) { + ctx, err := a.beforeGetIamPolicy.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + return a.next.GetIamPolicy(ctx, request) +} + +func (a *FreightServiceAuthorization) TestIamPermissions( + ctx context.Context, + request *v1.TestIamPermissionsRequest, +) (*v1.TestIamPermissionsResponse, error) { + iamauthz.Authorize(ctx) + return a.next.TestIamPermissions(ctx, request) +} diff --git a/proto/gen/einride/iam/v1/caller.pb.go b/proto/gen/einride/iam/v1/caller.pb.go index 20c04e69..a956e4a0 100644 --- a/proto/gen/einride/iam/v1/caller.pb.go +++ b/proto/gen/einride/iam/v1/caller.pb.go @@ -27,8 +27,8 @@ type Caller struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The caller's IAM policy member identity. - Member string `protobuf:"bytes,1,opt,name=member,proto3" json:"member,omitempty"` + // The authenticated IAM policy members of the caller. + Members []string `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` } func (x *Caller) Reset() { @@ -63,11 +63,11 @@ func (*Caller) Descriptor() ([]byte, []int) { return file_einride_iam_v1_caller_proto_rawDescGZIP(), []int{0} } -func (x *Caller) GetMember() string { +func (x *Caller) GetMembers() []string { if x != nil { - return x.Member + return x.Members } - return "" + return nil } var File_einride_iam_v1_caller_proto protoreflect.FileDescriptor @@ -77,17 +77,17 @@ var file_einride_iam_v1_caller_proto_rawDesc = []byte{ 0x2f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x20, - 0x0a, 0x06, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x42, 0x70, 0x0a, 0x13, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, - 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x32, 0x67, 0x6f, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, - 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, 0x69, 0x61, - 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x61, 0x6d, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x58, 0x58, - 0xaa, 0x02, 0x06, 0x49, 0x61, 0x6d, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x49, 0x61, 0x6d, 0x5c, - 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, + 0x0a, 0x06, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x42, 0x70, 0x0a, 0x13, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x65, 0x69, 0x6e, 0x72, 0x69, + 0x64, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x43, 0x61, 0x6c, 0x6c, 0x65, + 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x32, 0x67, 0x6f, 0x2e, 0x65, 0x69, 0x6e, + 0x72, 0x69, 0x64, 0x65, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x69, 0x6e, 0x72, 0x69, 0x64, 0x65, 0x2f, + 0x69, 0x61, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x61, 0x6d, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, + 0x58, 0x58, 0xaa, 0x02, 0x06, 0x49, 0x61, 0x6d, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x49, 0x61, + 0x6d, 0x5c, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/src/einride/iam/example/v1/freight_service.proto b/proto/src/einride/iam/example/v1/freight_service.proto index 57c882c1..1aa7b95a 100644 --- a/proto/src/einride/iam/example/v1/freight_service.proto +++ b/proto/src/einride/iam/example/v1/freight_service.proto @@ -208,10 +208,9 @@ service FreightService { }; option (einride.iam.v1.method_authorization) = { permission: "freight.sites.get" - after: { + before: { expression: - "test(caller, request.parent)" - " || test_all(caller, response.sites.map(s, s.name))" + "test(caller, request.parent) || test_all(caller, request.names)" description: "The caller must have permission to get all sites under the parent shipper" ", or the caller must have permission to get each of the requested sites." diff --git a/proto/src/einride/iam/v1/caller.proto b/proto/src/einride/iam/v1/caller.proto index d7def73d..25b00707 100644 --- a/proto/src/einride/iam/v1/caller.proto +++ b/proto/src/einride/iam/v1/caller.proto @@ -14,6 +14,6 @@ import "google/protobuf/timestamp.proto"; // Caller identity. message Caller { - // The caller's IAM policy member identity. - string member = 1; + // The authenticated IAM policy members of the caller. + repeated string members = 1; }