-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Protobuf plugin that generates IAM-specific service and method descriptors, and middleware that automatically checks policies using CEL Go.
- Loading branch information
Showing
14 changed files
with
997 additions
and
280 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package geniam | ||
|
||
import ( | ||
"strconv" | ||
|
||
"google.golang.org/protobuf/compiler/protogen" | ||
) | ||
|
||
type iamServiceDescriptorCodeGenerator 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 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 iamServiceDescriptorCodeGenerator) generateServiceDescriptorStruct(g *protogen.GeneratedFile) { | ||
g.P() | ||
g.P("type ", c.ServiceDescriptorStructGoName(), " struct {") | ||
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) | ||
} | ||
for _, method := range c.service.Methods { | ||
if getMethodAuthorizationOptions(method) == nil { | ||
continue | ||
} | ||
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("}") | ||
} | ||
} | ||
|
||
func (c iamServiceDescriptorCodeGenerator) generateServiceDescriptorVariable(g *protogen.GeneratedFile) { | ||
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", | ||
}) | ||
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("}") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package geniam | ||
|
||
import ( | ||
"google.golang.org/protobuf/compiler/protogen" | ||
) | ||
|
||
const Version = "development" | ||
|
||
const generatedFilenameSuffix = "_iam.go" | ||
|
||
func GenerateFile(gen *protogen.Plugin, f *protogen.File) { | ||
filename := f.GeneratedFilenamePrefix + generatedFilenameSuffix | ||
g := gen.NewGeneratedFile(filename, f.GoImportPath) | ||
g.Skip() | ||
g.P("package ", f.GoPackageName) | ||
g.P() | ||
for _, service := range f.Services { | ||
descriptor := iamServiceDescriptorCodeGenerator{gen: gen, file: f, service: service} | ||
descriptor.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("}") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
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" | ||
) | ||
|
||
func getPredefinedRoles(service *protogen.Service) *iamv1.Roles { | ||
return proto.GetExtension(service.Desc.Options(), iamv1.E_PredefinedRoles).(*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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"go.einride.tech/iam/cmd/protoc-gen-go-iam/internal/geniam" | ||
"google.golang.org/protobuf/compiler/protogen" | ||
) | ||
|
||
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) | ||
} | ||
protogen.Options{}.Run(func(gen *protogen.Plugin) error { | ||
for _, f := range gen.Files { | ||
if f.Generate { | ||
geniam.GenerateFile(gen, f) | ||
} | ||
} | ||
return nil | ||
}) | ||
} |
Oops, something went wrong.