/* * Copyright 2023 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rbac import ( "encoding/json" "fmt" "strings" v1xdsudpatypepb "github.com/cncf/xds/go/udpa/type/v1" v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3" v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" v3auditloggersstreampb "github.com/envoyproxy/go-control-plane/envoy/extensions/rbac/audit_loggers/stream/v3" "google.golang.org/grpc/authz/audit" "google.golang.org/grpc/authz/audit/stdout" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/structpb" ) func buildLogger(loggerConfig *v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig) (audit.Logger, error) { if loggerConfig.GetAuditLogger().GetTypedConfig() == nil { return nil, fmt.Errorf("missing required field: TypedConfig") } customConfig, loggerName, err := getCustomConfig(loggerConfig.AuditLogger.TypedConfig) if err != nil { return nil, err } if loggerName == "" { return nil, fmt.Errorf("field TypedConfig.TypeURL cannot be an empty string") } factory := audit.GetLoggerBuilder(loggerName) if factory == nil { if loggerConfig.IsOptional { return nil, nil } return nil, fmt.Errorf("no builder registered for %v", loggerName) } auditLoggerConfig, err := factory.ParseLoggerConfig(customConfig) if err != nil { return nil, fmt.Errorf("custom config could not be parsed by registered factory. error: %v", err) } auditLogger := factory.Build(auditLoggerConfig) return auditLogger, nil } func getCustomConfig(config *anypb.Any) (json.RawMessage, string, error) { c, err := config.UnmarshalNew() if err != nil { return nil, "", err } switch m := c.(type) { case *v1xdsudpatypepb.TypedStruct: return convertCustomConfig(m.TypeUrl, m.Value) case *v3xdsxdstypepb.TypedStruct: return convertCustomConfig(m.TypeUrl, m.Value) case *v3auditloggersstreampb.StdoutAuditLog: return convertStdoutConfig(m) } return nil, "", fmt.Errorf("custom config not implemented for type [%v]", config.GetTypeUrl()) } func convertStdoutConfig(config *v3auditloggersstreampb.StdoutAuditLog) (json.RawMessage, string, error) { json, err := protojson.Marshal(config) return json, stdout.Name, err } func convertCustomConfig(typeURL string, s *structpb.Struct) (json.RawMessage, string, error) { // The gRPC policy name will be the "type name" part of the value of the // type_url field in the TypedStruct. We get this by using the part after // the last / character. Can assume a valid type_url from the control plane. urls := strings.Split(typeURL, "/") if len(urls) == 0 { return nil, "", fmt.Errorf("error converting custom audit logger %v for %v: typeURL must have a url-like format with the typeName being the value after the last /", typeURL, s) } name := urls[len(urls)-1] rawJSON := []byte("{}") var err error if s != nil { rawJSON, err = json.Marshal(s) if err != nil { return nil, "", fmt.Errorf("error converting custom audit logger %v for %v: %v", typeURL, s, err) } } return rawJSON, name, nil }