Skip to content

Commit

Permalink
feat: initial implementation of CEL expression evaluation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odsod committed May 17, 2021
1 parent ce9d4e3 commit 9b91c2d
Show file tree
Hide file tree
Showing 25 changed files with 1,528 additions and 964 deletions.
7 changes: 7 additions & 0 deletions cmd/iamctl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
4 changes: 2 additions & 2 deletions cmd/iamctl/internal/examplecmd/exampleservercmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
189 changes: 189 additions & 0 deletions cmd/protoc-gen-go-iam/internal/geniam/authorization.go
Original file line number Diff line number Diff line change
@@ -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("}")
}
Loading

0 comments on commit 9b91c2d

Please sign in to comment.