From b9c92d4405ddabba8f57382e770aabbecd94eeaf Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Mon, 10 Feb 2025 20:40:47 +0100 Subject: [PATCH 01/19] WIP verification with bundle Signed-off-by: Jose I. Paris --- .../internal/action/workflow_run_describe.go | 46 +++++++++++++++++++ go.mod | 2 + go.sum | 2 + 3 files changed, 50 insertions(+) diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index 08c3155ca..e1335b74d 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -24,10 +24,13 @@ import ( "sort" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation" "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" sigs "github.com/sigstore/cosign/v2/pkg/signature" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + bundle2 "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" @@ -167,6 +170,12 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes return nil, err } + if attestation.Bundle != nil { + if err := verifyBundle(ctx, attestation.Bundle); err != nil { + return nil, err + } + } + if opts.Verify { if err := verifyEnvelope(ctx, envelope, opts); err != nil { action.cfg.Logger.Debug().Err(err).Msg("verifying the envelope") @@ -292,6 +301,43 @@ func materialPBToAction(in *pb.AttestationItem_Material) *Material { return m } +func verifyBundle(ctx context.Context, bundleBytes []byte) error { + chain, err := loadCertificates("devel/devkeys/ca.pub") + if err != nil { + return err + } + var bundle bundle2.Bundle + bundle.Bundle = new(protobundle.Bundle) + // unmarshal and validate + if err := bundle.UnmarshalJSON(bundleBytes); err != nil { + return err + } + pb := bundle.Bundle + if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { + // nothing to verify + return nil + } + + rawCert := pb.GetVerificationMaterial().GetCertificate().GetRawBytes() + signingCert, err := x509.ParseCertificate(rawCert) + if err != nil { + return err + } + + verifier, err := cosign.ValidateAndUnpackCertWithChain(signingCert, chain, &cosign.CheckOpts{IgnoreSCT: true}) + if err != nil { + return fmt.Errorf("validating the certificate: %w", err) + } + + dsseVerifier, err := dsse.NewEnvelopeVerifier(&sigdsee.VerifierAdapter{SignatureVerifier: verifier}) + if err != nil { + return fmt.Errorf("creating DSSE verifier: %w", err) + } + + _, err = dsseVerifier.Verify(ctx, attestation.DSSEEnvelopeFromBundle(pb)) + return err +} + func verifyEnvelope(ctx context.Context, e *dsse.Envelope, opts *WorkflowRunDescribeOpts) error { if opts.PublicKeyRef == "" && opts.CertPath == "" { return fmt.Errorf("no public key or cert path specified") diff --git a/go.mod b/go.mod index 5a009c55c..1bdc70ccd 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/sigstore/fulcio v1.6.3 github.com/sigstore/protobuf-specs v0.3.2 + github.com/sigstore/sigstore-go v0.6.1 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 @@ -185,6 +186,7 @@ require ( github.com/spiffe/go-spiffe/v2 v2.3.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect + github.com/theupdateframework/go-tuf/v2 v2.0.1 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect diff --git a/go.sum b/go.sum index df9587bc9..5c8a8e123 100644 --- a/go.sum +++ b/go.sum @@ -800,6 +800,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= From 6faa654879e92923766a2323b350c230eb6a233f Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 13:51:43 +0100 Subject: [PATCH 02/19] support multiple CAs Signed-off-by: Jose I. Paris --- app/controlplane/cmd/main.go | 32 +- app/controlplane/cmd/wire.go | 20 +- app/controlplane/cmd/wire_gen.go | 24 +- app/controlplane/configs/config.devel.yaml | 28 +- .../conf/controlplane/config/v1/conf.pb.go | 490 +++++++++--------- .../conf/controlplane/config/v1/conf.proto | 9 +- app/controlplane/pkg/biz/signing.go | 12 +- app/controlplane/pkg/ca/ca.go | 59 +++ app/controlplane/pkg/ca/ejbca/ejbca.go | 3 - app/controlplane/pkg/ca/fileca/fileca.go | 3 - 10 files changed, 388 insertions(+), 292 deletions(-) diff --git a/app/controlplane/cmd/main.go b/app/controlplane/cmd/main.go index 23c7b8bb4..b38ae0611 100644 --- a/app/controlplane/cmd/main.go +++ b/app/controlplane/cmd/main.go @@ -18,13 +18,11 @@ package main import ( "context" "fmt" + _ "net/http/pprof" "os" "time" "github.com/bufbuild/protovalidate-go" - "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca" - "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca/ejbca" - "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca/fileca" "github.com/getsentry/sentry-go" "github.com/nats-io/nats.go" flag "github.com/spf13/pflag" @@ -38,8 +36,6 @@ import ( "github.com/chainloop-dev/chainloop/pkg/credentials/manager" "github.com/chainloop-dev/chainloop/pkg/servicelogger" - _ "net/http/pprof" - "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config/env" @@ -143,12 +139,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - ca, err := newSigningCA(ctx, bc.GetCertificateAuthority(), logger) - if err != nil { - panic(err) - } - - app, cleanup, err := wireApp(&bc, credsWriter, logger, availablePlugins, ca) + app, cleanup, err := wireApp(&bc, credsWriter, logger, availablePlugins) if err != nil { panic(err) } @@ -251,22 +242,3 @@ func initSentry(c *conf.Bootstrap, logger log.Logger) (cleanupFunc func(), err e func newProtoValidator() (*protovalidate.Validator, error) { return protovalidate.New() } - -func newSigningCA(_ context.Context, ca *conf.CA, logger log.Logger) (ca.CertificateAuthority, error) { - // File - if ca.GetFileCa() != nil { - fileCa := ca.GetFileCa() - _ = logger.Log(log.LevelInfo, "msg", "Keyless: File CA configured") - return fileca.New(fileCa.GetCertPath(), fileCa.GetKeyPath(), fileCa.GetKeyPass(), false) - } - - if ca.GetEjbcaCa() != nil { - ejbcaCa := ca.GetEjbcaCa() - _ = logger.Log(log.LevelInfo, "msg", "Keyless: EJBCA CA configured") - return ejbca.New(ejbcaCa.GetServerUrl(), ejbcaCa.GetKeyPath(), ejbcaCa.GetCertPath(), ejbcaCa.GetRootCaPath(), ejbcaCa.GetCertificateProfileName(), ejbcaCa.GetEndEntityProfileName(), ejbcaCa.GetCertificateAuthorityName()) - } - - // No CA configured, keyless will be deactivated. - _ = logger.Log(log.LevelInfo, "msg", "Keyless Signing NOT configured") - return nil, nil -} diff --git a/app/controlplane/cmd/wire.go b/app/controlplane/cmd/wire.go index a3f8808d5..a78f201ef 100644 --- a/app/controlplane/cmd/wire.go +++ b/app/controlplane/cmd/wire.go @@ -21,6 +21,8 @@ package main import ( + "fmt" + conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" "github.com/chainloop-dev/chainloop/app/controlplane/internal/dispatcher" "github.com/chainloop-dev/chainloop/app/controlplane/internal/server" @@ -38,7 +40,7 @@ import ( "github.com/google/wire" ) -func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.AvailablePlugins, ca.CertificateAuthority) (*app, func(), error) { +func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.AvailablePlugins) (*app, func(), error) { panic( wire.Build( wire.Bind(new(credentials.Reader), new(credentials.ReaderWriter)), @@ -50,7 +52,7 @@ func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.Availabl wire.Bind(new(biz.CASClient), new(*biz.CASClientUseCase)), serviceOpts, wire.Value([]biz.CASClientOpts{}), - wire.FieldsOf(new(*conf.Bootstrap), "Server", "Auth", "Data", "CasServer", "ReferrerSharedIndex", "Onboarding", "PrometheusIntegration", "PolicyProviders", "NatsServer"), + wire.FieldsOf(new(*conf.Bootstrap), "Server", "Auth", "Data", "CasServer", "ReferrerSharedIndex", "Onboarding", "PrometheusIntegration", "PolicyProviders", "NatsServer", "CertificateAuthorities"), wire.FieldsOf(new(*conf.Data), "Database"), dispatcher.New, authz.NewDatabaseEnforcer, @@ -62,6 +64,7 @@ func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.Availabl newNatsConnection, auditor.NewAuditLogPublisher, newCASServerOptions, + newSigningCAs, ), ) } @@ -96,3 +99,16 @@ func newCASServerOptions(in *conf.Bootstrap_CASServer) *biz.CASServerDefaultOpts DefaultEntryMaxSize: in.GetDefaultEntryMaxSize(), } } + +func newSigningCAs(cas []*conf.CA, logger log.Logger) (*ca.CertificateAuthorities, error) { + authorities, err := ca.NewCertificateAuthoritiesFromConfig(cas, logger) + if err != nil { + return nil, fmt.Errorf("failed to create CA authorities: %w", err) + } + // No CA configured, keyless will be deactivated. + if len(authorities.GetAuthorities()) == 0 { + _ = logger.Log(log.LevelInfo, "msg", "Keyless Signing NOT configured") + return nil, nil + } + return authorities, nil +} diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index a02c8d3c4..eec0f2fb0 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -7,6 +7,7 @@ package main import ( + "fmt" "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" "github.com/chainloop-dev/chainloop/app/controlplane/internal/dispatcher" "github.com/chainloop-dev/chainloop/app/controlplane/internal/server" @@ -29,7 +30,7 @@ import ( // Injectors from wire.go: -func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, logger log.Logger, availablePlugins sdk.AvailablePlugins, certificateAuthority ca.CertificateAuthority) (*app, func(), error) { +func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, logger log.Logger, availablePlugins sdk.AvailablePlugins) (*app, func(), error) { confData := bootstrap.Data data_Database := confData.Database newConfig := newDataConf(data_Database) @@ -214,7 +215,13 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l } attestationStateService := service.NewAttestationStateService(newAttestationStateServiceOpt) userService := service.NewUserService(membershipUseCase, organizationUseCase, v5...) - signingUseCase := biz.NewChainloopSigningUseCase(certificateAuthority) + v7 := bootstrap.CertificateAuthorities + certificateAuthorities, err := newSigningCAs(v7, logger) + if err != nil { + cleanup() + return nil, nil, err + } + signingUseCase := biz.NewChainloopSigningUseCase(certificateAuthorities) signingService := service.NewSigningService(signingUseCase, v5...) prometheusService := service.NewPrometheusService(organizationUseCase, prometheusUseCase, v5...) validator, err := newProtoValidator() @@ -321,3 +328,16 @@ func newCASServerOptions(in *conf.Bootstrap_CASServer) *biz.CASServerDefaultOpts DefaultEntryMaxSize: in.GetDefaultEntryMaxSize(), } } + +func newSigningCAs(cas []*conf.CA, logger log.Logger) (*ca.CertificateAuthorities, error) { + authorities, err := ca.NewCertificateAuthoritiesFromConfig(cas, logger) + if err != nil { + return nil, fmt.Errorf("failed to create CA authorities: %w", err) + } + + if len(authorities.GetAuthorities()) == 0 { + _ = logger.Log(log.LevelInfo, "msg", "Keyless Signing NOT configured") + return nil, nil + } + return authorities, nil +} diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index 24ed9b65e..175f42f62 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -19,20 +19,22 @@ server: # nats_server: # uri: nats://0.0.0.0:4222 -certificate_authority: - file_ca: - cert_path: ${FILE_CA_CERT_PATH:../../devel/devkeys/ca.pub} - key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} - key_pass: chainloop -# ejbca_ca: -# server_url: "https://localhost/ejbca" -# key_path: "../../devel/devkeys/superadmin.key" -# cert_path: "../../devel/devkeys/superadmin.pem" -# root_ca_path: "../../devel/devkeys/ManagementCA.pem" +certificate_authorities: + - issuer: true + file_ca: + cert_path: ${FILE_CA_CERT_PATH:../../devel/devkeys/ca.pub} + key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} + key_pass: chainloop + +# - ejbca_ca: +# server_url: "https://localhost/ejbca" +# key_path: "../../devel/devkeys/superadmin.key" +# cert_path: "../../devel/devkeys/superadmin.pem" +# root_ca_path: "../../devel/devkeys/ManagementCA.pem" # -# certificate_profile_name: "PlainSigner" -# end_entity_profile_name: "PlainSigner" -# certificate_authority_name: "ManagementCA" +# certificate_profile_name: "PlainSigner" +# end_entity_profile_name: "PlainSigner" +# certificate_authority_name: "ManagementCA" # Directory where the plugins are located # NOTE: plugins have the form of chainloop-plugin- diff --git a/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go b/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go index 9c92d3e6c..71dc84fc5 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go @@ -56,8 +56,12 @@ type Bootstrap struct { PluginsDir string `protobuf:"bytes,7,opt,name=plugins_dir,json=pluginsDir,proto3" json:"plugins_dir,omitempty"` // Configuration about the shared referrer index ReferrerSharedIndex *ReferrerSharedIndex `protobuf:"bytes,8,opt,name=referrer_shared_index,json=referrerSharedIndex,proto3" json:"referrer_shared_index,omitempty"` - // The certificate authority used for keyless signing + // The certificate authority used for keyless signing (deprecated, use certificate_authorities instead) + // + // Deprecated: Marked as deprecated in controlplane/config/v1/conf.proto. CertificateAuthority *CA `protobuf:"bytes,9,opt,name=certificate_authority,json=certificateAuthority,proto3" json:"certificate_authority,omitempty"` + // Multiple certificate authorities, used for CA rotation + CertificateAuthorities []*CA `protobuf:"bytes,15,rep,name=certificate_authorities,json=certificateAuthorities,proto3" json:"certificate_authorities,omitempty"` // Configuration for onboarding users in organizations with specific roles Onboarding []*v11.OnboardingSpec `protobuf:"bytes,10,rep,name=onboarding,proto3" json:"onboarding,omitempty"` // Configuration to enable Prometheus integration for the specified organizations @@ -158,6 +162,7 @@ func (x *Bootstrap) GetReferrerSharedIndex() *ReferrerSharedIndex { return nil } +// Deprecated: Marked as deprecated in controlplane/config/v1/conf.proto. func (x *Bootstrap) GetCertificateAuthority() *CA { if x != nil { return x.CertificateAuthority @@ -165,6 +170,13 @@ func (x *Bootstrap) GetCertificateAuthority() *CA { return nil } +func (x *Bootstrap) GetCertificateAuthorities() []*CA { + if x != nil { + return x.CertificateAuthorities + } + return nil +} + func (x *Bootstrap) GetOnboarding() []*v11.OnboardingSpec { if x != nil { return x.Onboarding @@ -539,7 +551,8 @@ type CA struct { // // *CA_FileCa // *CA_EjbcaCa - Ca isCA_Ca `protobuf_oneof:"ca"` + Ca isCA_Ca `protobuf_oneof:"ca"` + Issuer bool `protobuf:"varint,3,opt,name=issuer,proto3" json:"issuer,omitempty"` // one and only one at a time when used in certificate_authorities field } func (x *CA) Reset() { @@ -595,6 +608,13 @@ func (x *CA) GetEjbcaCa() *CA_EJBCA { return nil } +func (x *CA) GetIssuer() bool { + if x != nil { + return x.Issuer + } + return false +} + type isCA_Ca interface { isCA_Ca() } @@ -1485,7 +1505,7 @@ var file_controlplane_config_v1_conf_proto_rawDesc = []byte{ 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf4, 0x0a, 0x0a, 0x09, 0x42, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcd, 0x0b, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x12, 0x36, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, @@ -1519,218 +1539,225 @@ var file_controlplane_config_v1_conf_proto_rawDesc = []byte{ 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x13, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, - 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4f, 0x0a, 0x15, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x53, 0x0a, 0x15, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x41, 0x52, 0x14, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x46, 0x0a, 0x0a, 0x6f, 0x6e, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x70, 0x65, 0x63, 0x52, 0x0a, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x12, 0x68, 0x0a, 0x16, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x69, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, - 0x68, 0x65, 0x75, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x70, 0x65, 0x63, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x10, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x0c, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, - 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x0f, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x27, 0x0a, - 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x72, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x72, 0x12, 0x4d, 0x0a, 0x0b, 0x6e, 0x61, 0x74, 0x73, 0x5f, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x4e, - 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x74, 0x73, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x9d, 0x01, 0x0a, 0x0d, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x4e, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x1a, 0x3c, 0x0a, 0x06, 0x53, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x64, 0x73, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, - 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0xc0, 0x01, 0x0a, 0x09, 0x43, 0x41, 0x53, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x04, 0x67, 0x72, 0x70, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x43, 0x41, 0x42, 0x02, 0x18, 0x01, 0x52, 0x14, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x53, 0x0a, 0x17, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x12, 0x46, 0x0a, 0x0a, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x18, + 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0a, 0x6f, + 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x68, 0x0a, 0x16, 0x70, 0x72, 0x6f, + 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x49, 0x6e, 0x74, + 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x15, 0x70, 0x72, + 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x10, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x72, 0x12, + 0x4d, 0x0a, 0x0b, 0x6e, 0x61, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, + 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x4e, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x9d, + 0x01, 0x0a, 0x0d, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x12, 0x4e, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, + 0x72, 0x61, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x2e, 0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x1a, 0x3c, 0x0a, 0x06, 0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x73, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x73, 0x6e, 0x12, 0x20, 0x0a, 0x0b, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0xc0, + 0x01, 0x0a, 0x09, 0x43, 0x41, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x04, + 0x67, 0x72, 0x70, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x52, 0x50, 0x43, 0x42, + 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, 0x67, 0x72, 0x70, 0x63, 0x12, 0x1a, 0x0a, + 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x33, 0x0a, 0x16, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, + 0x65, 0x1a, 0x27, 0x0a, 0x0a, 0x4e, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, + 0x19, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, + 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0xee, 0x01, 0x0a, 0x0e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x97, 0x01, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x82, 0x01, 0xba, + 0x48, 0x7f, 0xba, 0x01, 0x7c, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x64, 0x6e, 0x73, 0x2d, + 0x31, 0x31, 0x32, 0x33, 0x12, 0x3a, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x63, 0x61, 0x73, + 0x65, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x2e, + 0x1a, 0x2f, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x28, 0x27, + 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, + 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x27, + 0x29, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x12, 0x16, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x52, 0x0a, 0x13, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x4f, 0x72, 0x67, 0x73, 0x22, + 0xd3, 0x04, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x04, 0x68, 0x74, + 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x04, 0x68, + 0x74, 0x74, 0x70, 0x12, 0x37, 0x0a, 0x04, 0x67, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x47, 0x52, 0x50, 0x43, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x04, - 0x67, 0x72, 0x70, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x55, 0x72, 0x6c, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x1a, 0x27, 0x0a, 0x0a, 0x4e, 0x61, 0x74, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x75, 0x72, - 0x69, 0x22, 0xee, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x12, 0x97, 0x01, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x82, 0x01, 0xba, 0x48, 0x7f, 0xba, 0x01, 0x7c, 0x0a, 0x0d, 0x6e, 0x61, - 0x6d, 0x65, 0x2e, 0x64, 0x6e, 0x73, 0x2d, 0x31, 0x31, 0x32, 0x33, 0x12, 0x3a, 0x6d, 0x75, 0x73, - 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6c, - 0x6f, 0x77, 0x65, 0x72, 0x63, 0x61, 0x73, 0x65, 0x20, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x73, - 0x2c, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, - 0x79, 0x70, 0x68, 0x65, 0x6e, 0x73, 0x2e, 0x1a, 0x2f, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x73, 0x28, 0x27, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, - 0x28, 0x5b, 0x2d, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x30, - 0x2d, 0x39, 0x5d, 0x29, 0x3f, 0x24, 0x27, 0x29, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x22, 0x52, 0x0a, 0x13, 0x52, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x72, 0x53, 0x68, - 0x61, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x6f, - 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x64, 0x4f, 0x72, 0x67, 0x73, 0x22, 0xd3, 0x04, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x12, 0x37, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, - 0x48, 0x54, 0x54, 0x50, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x37, 0x0a, 0x04, 0x67, 0x72, - 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x52, 0x50, 0x43, 0x52, 0x04, 0x67, - 0x72, 0x70, 0x63, 0x12, 0x46, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x0b, - 0x68, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x8c, 0x01, 0x0a, 0x04, - 0x48, 0x54, 0x54, 0x50, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, - 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, - 0x64, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x55, 0x72, 0x6c, 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x72, 0x2e, 0x47, 0x52, 0x50, 0x43, 0x52, 0x04, 0x67, 0x72, 0x70, 0x63, 0x12, 0x46, 0x0a, 0x0c, + 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x1a, 0x8c, 0x01, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x12, 0x18, 0x0a, + 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x72, 0x6c, 0x12, 0x33, + 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x1a, 0x48, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0xb5, 0x01, + 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x12, 0x1b, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, + 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x4c, 0x53, 0x52, 0x09, 0x74, 0x6c, 0x73, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x9a, 0x02, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x41, + 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x1a, 0xce, 0x01, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x24, + 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x6f, 0x6e, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x70, 0x65, 0x6e, + 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, + 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x73, 0x12, 0x46, 0x0a, 0x12, 0x6d, 0x61, + 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x48, 0x0a, 0x03, 0x54, 0x4c, - 0x53, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x1a, 0xb5, 0x01, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x12, 0x18, 0x0a, - 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1b, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, - 0x61, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x41, 0x0a, 0x0a, 0x74, 0x6c, 0x73, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x4c, - 0x53, 0x52, 0x09, 0x74, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x9a, 0x02, 0x0a, - 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x1a, 0xce, 0x01, 0x0a, 0x08, 0x44, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x65, - 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, - 0x69, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x6d, - 0x61, 0x78, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, - 0x73, 0x12, 0x46, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x69, 0x64, - 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, - 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xa9, 0x04, 0x0a, 0x04, 0x41, 0x75, - 0x74, 0x68, 0x12, 0x39, 0x0a, 0x19, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x6a, 0x77, 0x73, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x4a, 0x77, 0x73, 0x48, 0x6d, 0x61, 0x63, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x45, 0x0a, - 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x2e, - 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x6f, 0x77, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x22, 0x63, 0x61, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, - 0x74, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x1d, 0x63, 0x61, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, - 0x35, 0x0a, 0x04, 0x6f, 0x69, 0x64, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x2e, 0x4f, 0x49, 0x44, 0x43, - 0x52, 0x04, 0x6f, 0x69, 0x64, 0x63, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x76, 0x55, 0x73, 0x65, - 0x72, 0x1a, 0x8e, 0x01, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x75, 0x72, - 0x6c, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x55, 0x72, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x1a, 0x71, 0x0a, 0x09, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, - 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xcc, 0x04, 0x0a, 0x02, 0x43, 0x41, 0x12, 0x3c, 0x0a, 0x07, - 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x41, - 0x48, 0x00, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x12, 0x3d, 0x0a, 0x08, 0x65, 0x6a, - 0x62, 0x63, 0x61, 0x5f, 0x63, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x41, 0x2e, 0x45, 0x4a, 0x42, 0x43, 0x41, 0x48, 0x00, - 0x52, 0x07, 0x65, 0x6a, 0x62, 0x63, 0x61, 0x43, 0x61, 0x1a, 0x5b, 0x0a, 0x06, 0x46, 0x69, 0x6c, - 0x65, 0x43, 0x41, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x50, 0x61, 0x74, 0x68, - 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6b, - 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, - 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x1a, 0xe5, 0x02, 0x0a, 0x05, 0x45, 0x4a, 0x42, 0x43, 0x41, - 0x12, 0x26, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x22, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, - 0x02, 0x10, 0x01, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x09, - 0x63, 0x65, 0x72, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x61, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x41, 0x0a, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, - 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x17, 0x65, 0x6e, 0x64, 0x5f, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, - 0x01, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x1a, 0x63, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x52, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x04, - 0x0a, 0x02, 0x63, 0x61, 0x22, 0x3f, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, - 0x75, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, - 0x63, 0x12, 0x22, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x07, 0x6f, 0x72, - 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x5f, 0x5a, 0x5d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, - 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x76, - 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x22, 0xa9, 0x04, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x19, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6a, 0x77, 0x73, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x4a, 0x77, 0x73, 0x48, 0x6d, 0x61, 0x63, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, + 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x49, 0x0a, + 0x22, 0x63, 0x61, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x63, 0x61, 0x73, 0x52, 0x6f, + 0x62, 0x6f, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x4b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x35, 0x0a, 0x04, 0x6f, 0x69, 0x64, 0x63, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x75, 0x74, 0x68, 0x2e, 0x4f, 0x49, 0x44, 0x43, 0x52, 0x04, 0x6f, 0x69, 0x64, 0x63, 0x12, + 0x19, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x64, 0x65, 0x76, 0x55, 0x73, 0x65, 0x72, 0x1a, 0x8e, 0x01, 0x0a, 0x04, 0x4f, + 0x49, 0x44, 0x43, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x2c, 0x0a, + 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, + 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x69, 0x6e, + 0x55, 0x72, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x1a, 0x71, 0x0a, 0x09, 0x41, + 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x25, + 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xe4, + 0x04, 0x0a, 0x02, 0x43, 0x41, 0x12, 0x3c, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x41, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x41, 0x48, 0x00, 0x52, 0x06, 0x66, 0x69, 0x6c, + 0x65, 0x43, 0x61, 0x12, 0x3d, 0x0a, 0x08, 0x65, 0x6a, 0x62, 0x63, 0x61, 0x5f, 0x63, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x41, 0x2e, 0x45, 0x4a, 0x42, 0x43, 0x41, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6a, 0x62, 0x63, 0x61, + 0x43, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x1a, 0x5b, 0x0a, 0x06, 0x46, 0x69, + 0x6c, 0x65, 0x43, 0x41, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6b, 0x65, 0x79, 0x50, 0x61, 0x73, 0x73, 0x1a, 0xe5, 0x02, 0x0a, 0x05, 0x45, 0x4a, 0x42, 0x43, + 0x41, 0x12, 0x26, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x22, 0x0a, 0x08, 0x6b, 0x65, 0x79, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, + 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x50, + 0x61, 0x74, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x43, + 0x61, 0x50, 0x61, 0x74, 0x68, 0x12, 0x41, 0x0a, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x17, 0x65, 0x6e, 0x64, 0x5f, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, + 0x10, 0x01, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x1a, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, + 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x42, + 0x04, 0x0a, 0x02, 0x63, 0x61, 0x22, 0x3f, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, + 0x65, 0x75, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, + 0x65, 0x63, 0x12, 0x22, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x07, 0x6f, + 0x72, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x5f, 0x5a, 0x5d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, + 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, + 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, + 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1780,29 +1807,30 @@ var file_controlplane_config_v1_conf_proto_depIdxs = []int32{ 9, // 5: controlplane.config.v1.Bootstrap.cas_server:type_name -> controlplane.config.v1.Bootstrap.CASServer 2, // 6: controlplane.config.v1.Bootstrap.referrer_shared_index:type_name -> controlplane.config.v1.ReferrerSharedIndex 6, // 7: controlplane.config.v1.Bootstrap.certificate_authority:type_name -> controlplane.config.v1.CA - 21, // 8: controlplane.config.v1.Bootstrap.onboarding:type_name -> controlplane.config.v1.OnboardingSpec - 7, // 9: controlplane.config.v1.Bootstrap.prometheus_integration:type_name -> controlplane.config.v1.PrometheusIntegrationSpec - 1, // 10: controlplane.config.v1.Bootstrap.policy_providers:type_name -> controlplane.config.v1.PolicyProvider - 10, // 11: controlplane.config.v1.Bootstrap.nats_server:type_name -> controlplane.config.v1.Bootstrap.NatsServer - 12, // 12: controlplane.config.v1.Server.http:type_name -> controlplane.config.v1.Server.HTTP - 14, // 13: controlplane.config.v1.Server.grpc:type_name -> controlplane.config.v1.Server.GRPC - 12, // 14: controlplane.config.v1.Server.http_metrics:type_name -> controlplane.config.v1.Server.HTTP - 15, // 15: controlplane.config.v1.Data.database:type_name -> controlplane.config.v1.Data.Database - 17, // 16: controlplane.config.v1.Auth.allow_list:type_name -> controlplane.config.v1.Auth.AllowList - 16, // 17: controlplane.config.v1.Auth.oidc:type_name -> controlplane.config.v1.Auth.OIDC - 18, // 18: controlplane.config.v1.CA.file_ca:type_name -> controlplane.config.v1.CA.FileCA - 19, // 19: controlplane.config.v1.CA.ejbca_ca:type_name -> controlplane.config.v1.CA.EJBCA - 11, // 20: controlplane.config.v1.Bootstrap.Observability.sentry:type_name -> controlplane.config.v1.Bootstrap.Observability.Sentry - 14, // 21: controlplane.config.v1.Bootstrap.CASServer.grpc:type_name -> controlplane.config.v1.Server.GRPC - 22, // 22: controlplane.config.v1.Server.HTTP.timeout:type_name -> google.protobuf.Duration - 22, // 23: controlplane.config.v1.Server.GRPC.timeout:type_name -> google.protobuf.Duration - 13, // 24: controlplane.config.v1.Server.GRPC.tls_config:type_name -> controlplane.config.v1.Server.TLS - 22, // 25: controlplane.config.v1.Data.Database.max_conn_idle_time:type_name -> google.protobuf.Duration - 26, // [26:26] is the sub-list for method output_type - 26, // [26:26] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 6, // 8: controlplane.config.v1.Bootstrap.certificate_authorities:type_name -> controlplane.config.v1.CA + 21, // 9: controlplane.config.v1.Bootstrap.onboarding:type_name -> controlplane.config.v1.OnboardingSpec + 7, // 10: controlplane.config.v1.Bootstrap.prometheus_integration:type_name -> controlplane.config.v1.PrometheusIntegrationSpec + 1, // 11: controlplane.config.v1.Bootstrap.policy_providers:type_name -> controlplane.config.v1.PolicyProvider + 10, // 12: controlplane.config.v1.Bootstrap.nats_server:type_name -> controlplane.config.v1.Bootstrap.NatsServer + 12, // 13: controlplane.config.v1.Server.http:type_name -> controlplane.config.v1.Server.HTTP + 14, // 14: controlplane.config.v1.Server.grpc:type_name -> controlplane.config.v1.Server.GRPC + 12, // 15: controlplane.config.v1.Server.http_metrics:type_name -> controlplane.config.v1.Server.HTTP + 15, // 16: controlplane.config.v1.Data.database:type_name -> controlplane.config.v1.Data.Database + 17, // 17: controlplane.config.v1.Auth.allow_list:type_name -> controlplane.config.v1.Auth.AllowList + 16, // 18: controlplane.config.v1.Auth.oidc:type_name -> controlplane.config.v1.Auth.OIDC + 18, // 19: controlplane.config.v1.CA.file_ca:type_name -> controlplane.config.v1.CA.FileCA + 19, // 20: controlplane.config.v1.CA.ejbca_ca:type_name -> controlplane.config.v1.CA.EJBCA + 11, // 21: controlplane.config.v1.Bootstrap.Observability.sentry:type_name -> controlplane.config.v1.Bootstrap.Observability.Sentry + 14, // 22: controlplane.config.v1.Bootstrap.CASServer.grpc:type_name -> controlplane.config.v1.Server.GRPC + 22, // 23: controlplane.config.v1.Server.HTTP.timeout:type_name -> google.protobuf.Duration + 22, // 24: controlplane.config.v1.Server.GRPC.timeout:type_name -> google.protobuf.Duration + 13, // 25: controlplane.config.v1.Server.GRPC.tls_config:type_name -> controlplane.config.v1.Server.TLS + 22, // 26: controlplane.config.v1.Data.Database.max_conn_idle_time:type_name -> google.protobuf.Duration + 27, // [27:27] is the sub-list for method output_type + 27, // [27:27] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name } func init() { file_controlplane_config_v1_conf_proto_init() } diff --git a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto index fce9a83bc..6d7da76c9 100644 --- a/app/controlplane/internal/conf/controlplane/config/v1/conf.proto +++ b/app/controlplane/internal/conf/controlplane/config/v1/conf.proto @@ -38,8 +38,11 @@ message Bootstrap { // Configuration about the shared referrer index ReferrerSharedIndex referrer_shared_index = 8; - // The certificate authority used for keyless signing - CA certificate_authority = 9; + // The certificate authority used for keyless signing (deprecated, use certificate_authorities instead) + CA certificate_authority = 9 [deprecated=true]; + + // Multiple certificate authorities, used for CA rotation + repeated CA certificate_authorities = 15; message Observability { Sentry sentry = 1; @@ -192,6 +195,8 @@ message CA { EJBCA ejbca_ca = 2; } + bool issuer = 3; // one and only one at a time when used in certificate_authorities field + message FileCA { string cert_path = 1; string key_path = 2; diff --git a/app/controlplane/pkg/biz/signing.go b/app/controlplane/pkg/biz/signing.go index fde208088..221a88be1 100644 --- a/app/controlplane/pkg/biz/signing.go +++ b/app/controlplane/pkg/biz/signing.go @@ -29,16 +29,16 @@ import ( ) type SigningUseCase struct { - CA ca.CertificateAuthority + CAs *ca.CertificateAuthorities } -func NewChainloopSigningUseCase(ca ca.CertificateAuthority) *SigningUseCase { - return &SigningUseCase{CA: ca} +func NewChainloopSigningUseCase(cas *ca.CertificateAuthorities) *SigningUseCase { + return &SigningUseCase{CAs: cas} } // CreateSigningCert signs a certificate request with a configured CA, and returns the full certificate chain func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, csrRaw []byte) ([]string, error) { - if s.CA == nil { + if s.CAs == nil { return nil, NewErrNotImplemented("CA not initialized") } @@ -66,8 +66,8 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, cs } // Create certificate from CA provider (no Signed Certificate Timestamps for now) - - csc, err := s.CA.CreateCertificateFromCSR(ctx, newChainloopPrincipal(orgID), csr) + issuerCA := s.CAs.GetSignerCA() + csc, err := issuerCA.CreateCertificateFromCSR(ctx, newChainloopPrincipal(orgID), csr) if err != nil { return nil, fmt.Errorf("creating certificate: %w", err) } diff --git a/app/controlplane/pkg/ca/ca.go b/app/controlplane/pkg/ca/ca.go index 5dd6e3571..c666300c8 100644 --- a/app/controlplane/pkg/ca/ca.go +++ b/app/controlplane/pkg/ca/ca.go @@ -18,7 +18,12 @@ package ca import ( "context" "crypto/x509" + "fmt" + conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca/ejbca" + "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca/fileca" + "github.com/go-kratos/kratos/v2/log" "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" ) @@ -27,3 +32,57 @@ type CertificateAuthority interface { // CreateCertificateFromCSR accepts a Certificate Request and generates a certificate signed by a signing authority CreateCertificateFromCSR(ctx context.Context, principal identity.Principal, csr *x509.CertificateRequest) (*ca.CodeSigningCertificate, error) } + +type CertificateAuthorities struct { + cas []CertificateAuthority + signerCA CertificateAuthority +} + +func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger) (*CertificateAuthorities, error) { + var ( + err error + authorities []CertificateAuthority + issuerCA CertificateAuthority + ) + + for _, configCA := range configCAs { + var authority CertificateAuthority + if configCA.GetFileCa() != nil { + fileCa := configCA.GetFileCa() + _ = logger.Log(log.LevelInfo, "msg", "Keyless: File CA configured") + authority, err = fileca.New(fileCa.GetCertPath(), fileCa.GetKeyPath(), fileCa.GetKeyPass(), false) + } + + if configCA.GetEjbcaCa() != nil { + ejbcaCa := configCA.GetEjbcaCa() + _ = logger.Log(log.LevelInfo, "msg", "Keyless: EJBCA CA configured") + authority, err = ejbca.New(ejbcaCa.GetServerUrl(), ejbcaCa.GetKeyPath(), ejbcaCa.GetCertPath(), ejbcaCa.GetRootCaPath(), ejbcaCa.GetCertificateProfileName(), ejbcaCa.GetEndEntityProfileName(), ejbcaCa.GetCertificateAuthorityName()) + } + if err != nil { + return nil, fmt.Errorf("failed to create file CA: %v", err) + } + if authority != nil { + authorities = append(authorities, authority) + if configCA.Issuer { + issuerCA = authority + } + } + } + + if len(authorities) > 0 && issuerCA == nil { + return nil, fmt.Errorf("at least one issuer CA has to be configured") + } + + return &CertificateAuthorities{ + cas: authorities, // it might be empty + signerCA: issuerCA, + }, nil +} + +func (cas *CertificateAuthorities) GetAuthorities() []CertificateAuthority { + return cas.cas +} + +func (cas *CertificateAuthorities) GetSignerCA() CertificateAuthority { + return cas.signerCA +} diff --git a/app/controlplane/pkg/ca/ejbca/ejbca.go b/app/controlplane/pkg/ca/ejbca/ejbca.go index 5fb17224c..251abe3db 100644 --- a/app/controlplane/pkg/ca/ejbca/ejbca.go +++ b/app/controlplane/pkg/ca/ejbca/ejbca.go @@ -28,7 +28,6 @@ import ( "net/http" "os" - "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca" fulcioca "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" ) @@ -51,8 +50,6 @@ type EJBCA struct { caName string } -var _ ca.CertificateAuthority = (*EJBCA)(nil) - func New(serverURL, keyPath, certPath, rootCAPath, certProfileName, endEntityProfileName, caName string) (*EJBCA, error) { if serverURL == "" || keyPath == "" || certPath == "" || certProfileName == "" || endEntityProfileName == "" || caName == "" { return nil, fmt.Errorf("ejbca: invalid arguments") diff --git a/app/controlplane/pkg/ca/fileca/fileca.go b/app/controlplane/pkg/ca/fileca/fileca.go index fe3c16965..203d024f2 100644 --- a/app/controlplane/pkg/ca/fileca/fileca.go +++ b/app/controlplane/pkg/ca/fileca/fileca.go @@ -20,7 +20,6 @@ import ( "crypto/x509" "fmt" - "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca" fulcioca "github.com/sigstore/fulcio/pkg/ca" fulciofileca "github.com/sigstore/fulcio/pkg/ca/fileca" "github.com/sigstore/fulcio/pkg/identity" @@ -30,8 +29,6 @@ type FileCA struct { ca fulcioca.CertificateAuthority } -var _ ca.CertificateAuthority = (*FileCA)(nil) - func New(certPath, keyPath, keyPass string, watch bool) (*FileCA, error) { wrappedCa, err := fulciofileca.NewFileCA(certPath, keyPath, keyPass, watch) if err != nil { From c6a1c03fd85a36df4dfc1052cf8f1851002df6bd Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 14:06:03 +0100 Subject: [PATCH 03/19] add service interface Signed-off-by: Jose I. Paris --- .../api/controlplane/v1/signing.pb.go | 183 +++++++++++-- .../api/controlplane/v1/signing.proto | 7 + .../api/controlplane/v1/signing_grpc.pb.go | 37 +++ .../gen/frontend/controlplane/v1/signing.ts | 252 ++++++++++++++++++ ...e.v1.GetTrustedRootRequest.jsonschema.json | 8 + ...plane.v1.GetTrustedRootRequest.schema.json | 8 + ....v1.GetTrustedRootResponse.jsonschema.json | 19 ++ ...lane.v1.GetTrustedRootResponse.schema.json | 19 ++ 8 files changed, 510 insertions(+), 23 deletions(-) create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.jsonschema.json create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.schema.json create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.jsonschema.json create mode 100644 app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.schema.json diff --git a/app/controlplane/api/controlplane/v1/signing.pb.go b/app/controlplane/api/controlplane/v1/signing.pb.go index d2d7ecdb8..753cdfa64 100644 --- a/app/controlplane/api/controlplane/v1/signing.pb.go +++ b/app/controlplane/api/controlplane/v1/signing.pb.go @@ -178,6 +178,92 @@ func (x *CertificateChain) GetCertificates() []string { return nil } +type GetTrustedRootRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetTrustedRootRequest) Reset() { + *x = GetTrustedRootRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controlplane_v1_signing_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTrustedRootRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTrustedRootRequest) ProtoMessage() {} + +func (x *GetTrustedRootRequest) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_signing_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTrustedRootRequest.ProtoReflect.Descriptor instead. +func (*GetTrustedRootRequest) Descriptor() ([]byte, []int) { + return file_controlplane_v1_signing_proto_rawDescGZIP(), []int{3} +} + +type GetTrustedRootResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // map keyID (cert SubjectKeyIdentifier) to PEM encoded chains + Keys map[string]*CertificateChain `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *GetTrustedRootResponse) Reset() { + *x = GetTrustedRootResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controlplane_v1_signing_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTrustedRootResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTrustedRootResponse) ProtoMessage() {} + +func (x *GetTrustedRootResponse) ProtoReflect() protoreflect.Message { + mi := &file_controlplane_v1_signing_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTrustedRootResponse.ProtoReflect.Descriptor instead. +func (*GetTrustedRootResponse) Descriptor() ([]byte, []int) { + return file_controlplane_v1_signing_proto_rawDescGZIP(), []int{4} +} + +func (x *GetTrustedRootResponse) GetKeys() map[string]*CertificateChain { + if x != nil { + return x.Keys + } + return nil +} + var File_controlplane_v1_signing_proto protoreflect.FileDescriptor var file_controlplane_v1_signing_proto_rawDesc = []byte{ @@ -201,20 +287,40 @@ var file_controlplane_v1_signing_proto_rawDesc = []byte{ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x73, 0x32, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x70, 0x0a, 0x13, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2b, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, - 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, - 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, - 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x74, 0x65, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x65, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbb, 0x01, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x6f, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x65, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x1a, + 0x5a, 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x37, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xe5, 0x01, 0x0a, 0x0e, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x70, + 0x0a, 0x13, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, + 0x67, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, + 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x61, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x52, 0x6f, + 0x6f, 0x74, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x52, + 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -229,21 +335,28 @@ func file_controlplane_v1_signing_proto_rawDescGZIP() []byte { return file_controlplane_v1_signing_proto_rawDescData } -var file_controlplane_v1_signing_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_controlplane_v1_signing_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_controlplane_v1_signing_proto_goTypes = []interface{}{ (*GenerateSigningCertRequest)(nil), // 0: controlplane.v1.GenerateSigningCertRequest (*GenerateSigningCertResponse)(nil), // 1: controlplane.v1.GenerateSigningCertResponse (*CertificateChain)(nil), // 2: controlplane.v1.CertificateChain + (*GetTrustedRootRequest)(nil), // 3: controlplane.v1.GetTrustedRootRequest + (*GetTrustedRootResponse)(nil), // 4: controlplane.v1.GetTrustedRootResponse + nil, // 5: controlplane.v1.GetTrustedRootResponse.KeysEntry } var file_controlplane_v1_signing_proto_depIdxs = []int32{ 2, // 0: controlplane.v1.GenerateSigningCertResponse.chain:type_name -> controlplane.v1.CertificateChain - 0, // 1: controlplane.v1.SigningService.GenerateSigningCert:input_type -> controlplane.v1.GenerateSigningCertRequest - 1, // 2: controlplane.v1.SigningService.GenerateSigningCert:output_type -> controlplane.v1.GenerateSigningCertResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 5, // 1: controlplane.v1.GetTrustedRootResponse.keys:type_name -> controlplane.v1.GetTrustedRootResponse.KeysEntry + 2, // 2: controlplane.v1.GetTrustedRootResponse.KeysEntry.value:type_name -> controlplane.v1.CertificateChain + 0, // 3: controlplane.v1.SigningService.GenerateSigningCert:input_type -> controlplane.v1.GenerateSigningCertRequest + 3, // 4: controlplane.v1.SigningService.GetTrustedRoot:input_type -> controlplane.v1.GetTrustedRootRequest + 1, // 5: controlplane.v1.SigningService.GenerateSigningCert:output_type -> controlplane.v1.GenerateSigningCertResponse + 4, // 6: controlplane.v1.SigningService.GetTrustedRoot:output_type -> controlplane.v1.GetTrustedRootResponse + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_controlplane_v1_signing_proto_init() } @@ -288,6 +401,30 @@ func file_controlplane_v1_signing_proto_init() { return nil } } + file_controlplane_v1_signing_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTrustedRootRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controlplane_v1_signing_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTrustedRootResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -295,7 +432,7 @@ func file_controlplane_v1_signing_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controlplane_v1_signing_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/app/controlplane/api/controlplane/v1/signing.proto b/app/controlplane/api/controlplane/v1/signing.proto index a3b1a035e..bfbad8712 100644 --- a/app/controlplane/api/controlplane/v1/signing.proto +++ b/app/controlplane/api/controlplane/v1/signing.proto @@ -25,6 +25,7 @@ import "buf/validate/validate.proto"; service SigningService { // GenerateSigningCert takes a certificate request and generates a new certificate for attestation signing rpc GenerateSigningCert (GenerateSigningCertRequest) returns (GenerateSigningCertResponse); + rpc GetTrustedRoot (GetTrustedRootRequest) returns (GetTrustedRootResponse); } message GenerateSigningCertRequest { @@ -41,3 +42,9 @@ message CertificateChain { */ repeated string certificates = 1; } + +message GetTrustedRootRequest {} +message GetTrustedRootResponse { + // map keyID (cert SubjectKeyIdentifier) to PEM encoded chains + map keys = 1; +} \ No newline at end of file diff --git a/app/controlplane/api/controlplane/v1/signing_grpc.pb.go b/app/controlplane/api/controlplane/v1/signing_grpc.pb.go index 277d1a2d9..fd5dff882 100644 --- a/app/controlplane/api/controlplane/v1/signing_grpc.pb.go +++ b/app/controlplane/api/controlplane/v1/signing_grpc.pb.go @@ -35,6 +35,7 @@ const _ = grpc.SupportPackageIsVersion7 const ( SigningService_GenerateSigningCert_FullMethodName = "/controlplane.v1.SigningService/GenerateSigningCert" + SigningService_GetTrustedRoot_FullMethodName = "/controlplane.v1.SigningService/GetTrustedRoot" ) // SigningServiceClient is the client API for SigningService service. @@ -43,6 +44,7 @@ const ( type SigningServiceClient interface { // GenerateSigningCert takes a certificate request and generates a new certificate for attestation signing GenerateSigningCert(ctx context.Context, in *GenerateSigningCertRequest, opts ...grpc.CallOption) (*GenerateSigningCertResponse, error) + GetTrustedRoot(ctx context.Context, in *GetTrustedRootRequest, opts ...grpc.CallOption) (*GetTrustedRootResponse, error) } type signingServiceClient struct { @@ -62,12 +64,22 @@ func (c *signingServiceClient) GenerateSigningCert(ctx context.Context, in *Gene return out, nil } +func (c *signingServiceClient) GetTrustedRoot(ctx context.Context, in *GetTrustedRootRequest, opts ...grpc.CallOption) (*GetTrustedRootResponse, error) { + out := new(GetTrustedRootResponse) + err := c.cc.Invoke(ctx, SigningService_GetTrustedRoot_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SigningServiceServer is the server API for SigningService service. // All implementations must embed UnimplementedSigningServiceServer // for forward compatibility type SigningServiceServer interface { // GenerateSigningCert takes a certificate request and generates a new certificate for attestation signing GenerateSigningCert(context.Context, *GenerateSigningCertRequest) (*GenerateSigningCertResponse, error) + GetTrustedRoot(context.Context, *GetTrustedRootRequest) (*GetTrustedRootResponse, error) mustEmbedUnimplementedSigningServiceServer() } @@ -78,6 +90,9 @@ type UnimplementedSigningServiceServer struct { func (UnimplementedSigningServiceServer) GenerateSigningCert(context.Context, *GenerateSigningCertRequest) (*GenerateSigningCertResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GenerateSigningCert not implemented") } +func (UnimplementedSigningServiceServer) GetTrustedRoot(context.Context, *GetTrustedRootRequest) (*GetTrustedRootResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTrustedRoot not implemented") +} func (UnimplementedSigningServiceServer) mustEmbedUnimplementedSigningServiceServer() {} // UnsafeSigningServiceServer may be embedded to opt out of forward compatibility for this service. @@ -109,6 +124,24 @@ func _SigningService_GenerateSigningCert_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _SigningService_GetTrustedRoot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTrustedRootRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SigningServiceServer).GetTrustedRoot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SigningService_GetTrustedRoot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SigningServiceServer).GetTrustedRoot(ctx, req.(*GetTrustedRootRequest)) + } + return interceptor(ctx, in, info, handler) +} + // SigningService_ServiceDesc is the grpc.ServiceDesc for SigningService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -120,6 +153,10 @@ var SigningService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GenerateSigningCert", Handler: _SigningService_GenerateSigningCert_Handler, }, + { + MethodName: "GetTrustedRoot", + Handler: _SigningService_GetTrustedRoot_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "controlplane/v1/signing.proto", diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/signing.ts b/app/controlplane/api/gen/frontend/controlplane/v1/signing.ts index d6abae963..e7caa433e 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/signing.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/signing.ts @@ -18,6 +18,19 @@ export interface CertificateChain { certificates: string[]; } +export interface GetTrustedRootRequest { +} + +export interface GetTrustedRootResponse { + /** map keyID (cert SubjectKeyIdentifier) to PEM encoded chains */ + keys: { [key: string]: CertificateChain }; +} + +export interface GetTrustedRootResponse_KeysEntry { + key: string; + value?: CertificateChain; +} + function createBaseGenerateSigningCertRequest(): GenerateSigningCertRequest { return { certificateSigningRequest: new Uint8Array(0) }; } @@ -199,12 +212,216 @@ export const CertificateChain = { }, }; +function createBaseGetTrustedRootRequest(): GetTrustedRootRequest { + return {}; +} + +export const GetTrustedRootRequest = { + encode(_: GetTrustedRootRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetTrustedRootRequest { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetTrustedRootRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(_: any): GetTrustedRootRequest { + return {}; + }, + + toJSON(_: GetTrustedRootRequest): unknown { + const obj: any = {}; + return obj; + }, + + create, I>>(base?: I): GetTrustedRootRequest { + return GetTrustedRootRequest.fromPartial(base ?? {}); + }, + + fromPartial, I>>(_: I): GetTrustedRootRequest { + const message = createBaseGetTrustedRootRequest(); + return message; + }, +}; + +function createBaseGetTrustedRootResponse(): GetTrustedRootResponse { + return { keys: {} }; +} + +export const GetTrustedRootResponse = { + encode(message: GetTrustedRootResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + Object.entries(message.keys).forEach(([key, value]) => { + GetTrustedRootResponse_KeysEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).ldelim(); + }); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetTrustedRootResponse { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetTrustedRootResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + const entry1 = GetTrustedRootResponse_KeysEntry.decode(reader, reader.uint32()); + if (entry1.value !== undefined) { + message.keys[entry1.key] = entry1.value; + } + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetTrustedRootResponse { + return { + keys: isObject(object.keys) + ? Object.entries(object.keys).reduce<{ [key: string]: CertificateChain }>((acc, [key, value]) => { + acc[key] = CertificateChain.fromJSON(value); + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: GetTrustedRootResponse): unknown { + const obj: any = {}; + obj.keys = {}; + if (message.keys) { + Object.entries(message.keys).forEach(([k, v]) => { + obj.keys[k] = CertificateChain.toJSON(v); + }); + } + return obj; + }, + + create, I>>(base?: I): GetTrustedRootResponse { + return GetTrustedRootResponse.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): GetTrustedRootResponse { + const message = createBaseGetTrustedRootResponse(); + message.keys = Object.entries(object.keys ?? {}).reduce<{ [key: string]: CertificateChain }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = CertificateChain.fromPartial(value); + } + return acc; + }, + {}, + ); + return message; + }, +}; + +function createBaseGetTrustedRootResponse_KeysEntry(): GetTrustedRootResponse_KeysEntry { + return { key: "", value: undefined }; +} + +export const GetTrustedRootResponse_KeysEntry = { + encode(message: GetTrustedRootResponse_KeysEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + CertificateChain.encode(message.value, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetTrustedRootResponse_KeysEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetTrustedRootResponse_KeysEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.value = CertificateChain.decode(reader, reader.uint32()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetTrustedRootResponse_KeysEntry { + return { + key: isSet(object.key) ? String(object.key) : "", + value: isSet(object.value) ? CertificateChain.fromJSON(object.value) : undefined, + }; + }, + + toJSON(message: GetTrustedRootResponse_KeysEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = message.value ? CertificateChain.toJSON(message.value) : undefined); + return obj; + }, + + create, I>>( + base?: I, + ): GetTrustedRootResponse_KeysEntry { + return GetTrustedRootResponse_KeysEntry.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I, + ): GetTrustedRootResponse_KeysEntry { + const message = createBaseGetTrustedRootResponse_KeysEntry(); + message.key = object.key ?? ""; + message.value = (object.value !== undefined && object.value !== null) + ? CertificateChain.fromPartial(object.value) + : undefined; + return message; + }, +}; + export interface SigningService { /** GenerateSigningCert takes a certificate request and generates a new certificate for attestation signing */ GenerateSigningCert( request: DeepPartial, metadata?: grpc.Metadata, ): Promise; + GetTrustedRoot( + request: DeepPartial, + metadata?: grpc.Metadata, + ): Promise; } export class SigningServiceClientImpl implements SigningService { @@ -213,6 +430,7 @@ export class SigningServiceClientImpl implements SigningService { constructor(rpc: Rpc) { this.rpc = rpc; this.GenerateSigningCert = this.GenerateSigningCert.bind(this); + this.GetTrustedRoot = this.GetTrustedRoot.bind(this); } GenerateSigningCert( @@ -225,6 +443,13 @@ export class SigningServiceClientImpl implements SigningService { metadata, ); } + + GetTrustedRoot( + request: DeepPartial, + metadata?: grpc.Metadata, + ): Promise { + return this.rpc.unary(SigningServiceGetTrustedRootDesc, GetTrustedRootRequest.fromPartial(request), metadata); + } } export const SigningServiceDesc = { serviceName: "controlplane.v1.SigningService" }; @@ -252,6 +477,29 @@ export const SigningServiceGenerateSigningCertDesc: UnaryMethodDefinitionish = { } as any, }; +export const SigningServiceGetTrustedRootDesc: UnaryMethodDefinitionish = { + methodName: "GetTrustedRoot", + service: SigningServiceDesc, + requestStream: false, + responseStream: false, + requestType: { + serializeBinary() { + return GetTrustedRootRequest.encode(this).finish(); + }, + } as any, + responseType: { + deserializeBinary(data: Uint8Array) { + const value = GetTrustedRootResponse.decode(data); + return { + ...value, + toObject() { + return value; + }, + }; + }, + } as any, +}; + interface UnaryMethodDefinitionishR extends grpc.UnaryMethodDefinition { requestStream: any; responseStream: any; @@ -375,6 +623,10 @@ type KeysOfUnion = T extends T ? keyof T : never; export type Exact = P extends Builtin ? P : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + function isSet(value: any): boolean { return value !== null && value !== undefined; } diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.jsonschema.json new file mode 100644 index 000000000..3a2d4e674 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.jsonschema.json @@ -0,0 +1,8 @@ +{ + "$id": "controlplane.v1.GetTrustedRootRequest.jsonschema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": {}, + "title": "Get Trusted Root Request", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.schema.json new file mode 100644 index 000000000..5d2922354 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootRequest.schema.json @@ -0,0 +1,8 @@ +{ + "$id": "controlplane.v1.GetTrustedRootRequest.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": {}, + "title": "Get Trusted Root Request", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.jsonschema.json new file mode 100644 index 000000000..522ff3539 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.jsonschema.json @@ -0,0 +1,19 @@ +{ + "$id": "controlplane.v1.GetTrustedRootResponse.jsonschema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "keys": { + "additionalProperties": { + "$ref": "controlplane.v1.CertificateChain.jsonschema.json" + }, + "description": "map keyID (cert SubjectKeyIdentifier) to PEM encoded chains", + "propertyNames": { + "type": "string" + }, + "type": "object" + } + }, + "title": "Get Trusted Root Response", + "type": "object" +} diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.schema.json new file mode 100644 index 000000000..4009b6493 --- /dev/null +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.GetTrustedRootResponse.schema.json @@ -0,0 +1,19 @@ +{ + "$id": "controlplane.v1.GetTrustedRootResponse.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "keys": { + "additionalProperties": { + "$ref": "controlplane.v1.CertificateChain.schema.json" + }, + "description": "map keyID (cert SubjectKeyIdentifier) to PEM encoded chains", + "propertyNames": { + "type": "string" + }, + "type": "object" + } + }, + "title": "Get Trusted Root Response", + "type": "object" +} From b1073f726a5e7f02175732d8f311d655fccb9190 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 18:47:54 +0100 Subject: [PATCH 04/19] implement service Signed-off-by: Jose I. Paris --- .../api/controlplane/v1/signing.proto | 2 +- app/controlplane/internal/service/signing.go | 17 ++++++++++ app/controlplane/pkg/biz/signing.go | 34 ++++++++++++++++++- app/controlplane/pkg/biz/signing_test.go | 29 +++++++++++++++- app/controlplane/pkg/ca/ca.go | 31 ++++++++++------- app/controlplane/pkg/ca/ejbca/ejbca.go | 22 +++++++++--- app/controlplane/pkg/ca/fileca/fileca.go | 8 +++++ 7 files changed, 123 insertions(+), 20 deletions(-) diff --git a/app/controlplane/api/controlplane/v1/signing.proto b/app/controlplane/api/controlplane/v1/signing.proto index bfbad8712..3819165ca 100644 --- a/app/controlplane/api/controlplane/v1/signing.proto +++ b/app/controlplane/api/controlplane/v1/signing.proto @@ -47,4 +47,4 @@ message GetTrustedRootRequest {} message GetTrustedRootResponse { // map keyID (cert SubjectKeyIdentifier) to PEM encoded chains map keys = 1; -} \ No newline at end of file +} diff --git a/app/controlplane/internal/service/signing.go b/app/controlplane/internal/service/signing.go index dc2b64e19..e6e9ae995 100644 --- a/app/controlplane/internal/service/signing.go +++ b/app/controlplane/internal/service/signing.go @@ -53,3 +53,20 @@ func (s *SigningService) GenerateSigningCert(ctx context.Context, req *v1.Genera return &v1.GenerateSigningCertResponse{Chain: &v1.CertificateChain{Certificates: certs}}, nil } + +func (s *SigningService) GetTrustedRoot(ctx context.Context, req *v1.GetTrustedRootRequest) (*v1.GetTrustedRootResponse, error) { + ra := usercontext.CurrentRobotAccount(ctx) + if ra == nil { + return nil, errors.Unauthorized("missing org", "authentication data is required") + } + + tr, err := s.signing.GetTrustedRoot(ctx) + if err != nil { + return nil, handleUseCaseErr(err, s.log) + } + resp := &v1.GetTrustedRootResponse{Keys: make(map[string]*v1.CertificateChain)} + for k, v := range tr.Keys { + resp.Keys[k] = &v1.CertificateChain{Certificates: v} + } + return resp, nil +} diff --git a/app/controlplane/pkg/biz/signing.go b/app/controlplane/pkg/biz/signing.go index 221a88be1..19ab40c31 100644 --- a/app/controlplane/pkg/biz/signing.go +++ b/app/controlplane/pkg/biz/signing.go @@ -18,6 +18,7 @@ package biz import ( "context" "crypto" + "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "errors" @@ -66,7 +67,10 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, cs } // Create certificate from CA provider (no Signed Certificate Timestamps for now) - issuerCA := s.CAs.GetSignerCA() + issuerCA, err := s.CAs.GetSignerCA() + if err != nil { + return nil, fmt.Errorf("getting signer CA: %w", err) + } csc, err := issuerCA.CreateCertificateFromCSR(ctx, newChainloopPrincipal(orgID), csr) if err != nil { return nil, fmt.Errorf("creating certificate: %w", err) @@ -87,6 +91,34 @@ func (s *SigningUseCase) CreateSigningCert(ctx context.Context, orgID string, cs return append([]string{finalPEM}, finalChainPEM...), nil } +func (s *SigningUseCase) GetTrustedRoot(ctx context.Context) (*TrustedRoot, error) { + trustedRoot := &TrustedRoot{Keys: make(map[string][]string)} + for _, auth := range s.CAs.GetAuthorities() { + chain, err := auth.GetRootChain(ctx) + if err != nil { + return nil, fmt.Errorf("getting root chain: %w", err) + } + if chain == nil || len(chain) == 0 { + continue + } + keyID := fmt.Sprintf("%x", sha256.Sum256(chain[0].SubjectKeyId)) + for _, cert := range chain { + pemCert, err := cryptoutils.MarshalCertificateToPEM(cert) + if err != nil { + return nil, fmt.Errorf("marshaling certificate to PEM: %w", err) + } + trustedRoot.Keys[keyID] = append(trustedRoot.Keys[keyID], string(pemCert)) + } + } + + return trustedRoot, nil +} + +type TrustedRoot struct { + // map of keyID and PEM encoded certificates + Keys map[string][]string +} + type chainloopPrincipal struct { orgID string } diff --git a/app/controlplane/pkg/biz/signing_test.go b/app/controlplane/pkg/biz/signing_test.go index a01c6f3c0..840a201de 100644 --- a/app/controlplane/pkg/biz/signing_test.go +++ b/app/controlplane/pkg/biz/signing_test.go @@ -27,6 +27,7 @@ import ( "testing" "github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz" + ca2 "github.com/chainloop-dev/chainloop/app/controlplane/pkg/ca" fulcioca "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/ca/ephemeralca" "github.com/sigstore/fulcio/pkg/identity" @@ -48,6 +49,14 @@ func (e TestCA) CreateCertificateFromCSR(ctx context.Context, principal identity return e.ca.CreateCertificate(ctx, principal, csr.PublicKey) } +func (e TestCA) GetRootChain(ctx context.Context) ([]*x509.Certificate, error) { + tb, err := e.ca.TrustBundle(ctx) + if err != nil { + return nil, err + } + return tb[0], nil +} + func NewTestCA() (*TestCA, error) { ca, err := ephemeralca.NewEphemeralCA() if err != nil { @@ -77,6 +86,24 @@ func (s *signingUseCaseTestSuite) TestSigningUseCase_CreateSigningCert() { }) } +func (s *signingUseCaseTestSuite) TestSigningUseCase_GetRootChain() { + s.Run("gets the chain", func() { + r, err := s.uc.GetTrustedRoot(context.TODO()) + s.NoError(err) + s.Len(r.Keys, 1) + }) + + s.Run("it's a valid certificate", func() { + r, err := s.uc.GetTrustedRoot(context.TODO()) + s.NoError(err) + for k, v := range r.Keys { + s.NotEmpty(k) + s.Len(v, 1) + s.Contains(v[0], "-----BEGIN CERTIFICATE-----") + } + }) +} + func TestSuite(t *testing.T) { suite.Run(t, new(signingUseCaseTestSuite)) } @@ -88,7 +115,7 @@ func (s *signingUseCaseTestSuite) SetupTest() { ca, err := NewTestCA() s.Require().NoError(err) - s.uc = &biz.SigningUseCase{CA: ca} + s.uc = &biz.SigningUseCase{CAs: &ca2.CertificateAuthorities{CAs: []ca2.CertificateAuthority{ca}}} } func createCSR() ([]byte, error) { diff --git a/app/controlplane/pkg/ca/ca.go b/app/controlplane/pkg/ca/ca.go index c666300c8..3245c395f 100644 --- a/app/controlplane/pkg/ca/ca.go +++ b/app/controlplane/pkg/ca/ca.go @@ -31,11 +31,12 @@ import ( type CertificateAuthority interface { // CreateCertificateFromCSR accepts a Certificate Request and generates a certificate signed by a signing authority CreateCertificateFromCSR(ctx context.Context, principal identity.Principal, csr *x509.CertificateRequest) (*ca.CodeSigningCertificate, error) + GetRootChain(ctx context.Context) ([]*x509.Certificate, error) } type CertificateAuthorities struct { - cas []CertificateAuthority - signerCA CertificateAuthority + CAs []CertificateAuthority + SignerCA CertificateAuthority } func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger) (*CertificateAuthorities, error) { @@ -59,7 +60,7 @@ func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger authority, err = ejbca.New(ejbcaCa.GetServerUrl(), ejbcaCa.GetKeyPath(), ejbcaCa.GetCertPath(), ejbcaCa.GetRootCaPath(), ejbcaCa.GetCertificateProfileName(), ejbcaCa.GetEndEntityProfileName(), ejbcaCa.GetCertificateAuthorityName()) } if err != nil { - return nil, fmt.Errorf("failed to create file CA: %v", err) + return nil, fmt.Errorf("failed to create file CA: %w", err) } if authority != nil { authorities = append(authorities, authority) @@ -69,20 +70,24 @@ func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger } } - if len(authorities) > 0 && issuerCA == nil { - return nil, fmt.Errorf("at least one issuer CA has to be configured") - } - return &CertificateAuthorities{ - cas: authorities, // it might be empty - signerCA: issuerCA, + CAs: authorities, // it might be empty + SignerCA: issuerCA, }, nil } -func (cas *CertificateAuthorities) GetAuthorities() []CertificateAuthority { - return cas.cas +func (c *CertificateAuthorities) GetAuthorities() []CertificateAuthority { + return c.CAs } -func (cas *CertificateAuthorities) GetSignerCA() CertificateAuthority { - return cas.signerCA +func (c *CertificateAuthorities) GetSignerCA() (CertificateAuthority, error) { + if c.SignerCA != nil { + return c.SignerCA, nil + } + + if len(c.CAs) > 0 { + return c.CAs[0], nil + } + + return nil, fmt.Errorf("no signer CA found") } diff --git a/app/controlplane/pkg/ca/ejbca/ejbca.go b/app/controlplane/pkg/ca/ejbca/ejbca.go index 251abe3db..a3ab1f38d 100644 --- a/app/controlplane/pkg/ca/ejbca/ejbca.go +++ b/app/controlplane/pkg/ca/ejbca/ejbca.go @@ -30,6 +30,7 @@ import ( fulcioca "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/identity" + "github.com/sigstore/sigstore/pkg/cryptoutils" ) type EJBCA struct { @@ -143,24 +144,24 @@ func (e EJBCA) CreateCertificateFromCSR(ctx context.Context, principal identity. return nil, fmt.Errorf("wrong status creating certificate: %v", resp.Status) } - var response response + var rsp response body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("unable to read response body: %w", err) } - if err := json.Unmarshal(body, &response); err != nil { + if err := json.Unmarshal(body, &rsp); err != nil { return nil, fmt.Errorf("unable to parse response body: %w", err) } // Decode certificate - derCert, err := base64.StdEncoding.DecodeString(response.Certificate) + derCert, err := base64.StdEncoding.DecodeString(rsp.Certificate) if err != nil { return nil, fmt.Errorf("unable to decode response body: %w", err) } // Decode chain chain := make([]*x509.Certificate, 0) - for _, c := range response.CertificateChain { + for _, c := range rsp.CertificateChain { decodedCert, err := base64.StdEncoding.DecodeString(c) if err != nil { return nil, fmt.Errorf("unable to decode response body: %w", err) @@ -174,3 +175,16 @@ func (e EJBCA) CreateCertificateFromCSR(ctx context.Context, principal identity. return fulcioca.CreateCSCFromDER(derCert, chain) } + +func (e EJBCA) GetRootChain(_ context.Context) ([]*x509.Certificate, error) { + // current implementation relies on the rootCAPath from the config. Future implementation should use + // /v1/ca and /v1/ca/{subject_dn}/certificate/download APIs instead + if e.rootCAPath != "" { + caCert, err := os.ReadFile(e.rootCAPath) + if err != nil { + return nil, fmt.Errorf("error reading root CA: %w", err) + } + return cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(caCert)) + } + return nil, nil +} diff --git a/app/controlplane/pkg/ca/fileca/fileca.go b/app/controlplane/pkg/ca/fileca/fileca.go index 203d024f2..2a42c9d66 100644 --- a/app/controlplane/pkg/ca/fileca/fileca.go +++ b/app/controlplane/pkg/ca/fileca/fileca.go @@ -42,3 +42,11 @@ func New(certPath, keyPath, keyPass string, watch bool) (*FileCA, error) { func (f FileCA) CreateCertificateFromCSR(ctx context.Context, principal identity.Principal, csr *x509.CertificateRequest) (*fulcioca.CodeSigningCertificate, error) { return f.ca.CreateCertificate(ctx, principal, csr.PublicKey) } + +func (f FileCA) GetRootChain(ctx context.Context) ([]*x509.Certificate, error) { + tb, err := f.ca.TrustBundle(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load trust bundle: %w", err) + } + return tb[0], nil +} From 2d1194ce84f99dfb3b0238288c531e59f62f58c4 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 19:08:05 +0100 Subject: [PATCH 05/19] lint Signed-off-by: Jose I. Paris --- app/controlplane/configs/config.devel.yaml | 18 +++++++++--------- app/controlplane/internal/service/signing.go | 2 +- app/controlplane/pkg/biz/signing.go | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index 175f42f62..bab111c68 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -26,15 +26,15 @@ certificate_authorities: key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} key_pass: chainloop -# - ejbca_ca: -# server_url: "https://localhost/ejbca" -# key_path: "../../devel/devkeys/superadmin.key" -# cert_path: "../../devel/devkeys/superadmin.pem" -# root_ca_path: "../../devel/devkeys/ManagementCA.pem" -# -# certificate_profile_name: "PlainSigner" -# end_entity_profile_name: "PlainSigner" -# certificate_authority_name: "ManagementCA" + - ejbca_ca: + server_url: "https://localhost/ejbca" + key_path: "../../devel/devkeys/superadmin.key" + cert_path: "../../devel/devkeys/superadmin.pem" + root_ca_path: "../../../keyfactor/ManagementCA.pem" + + certificate_profile_name: "PlainSigner" + end_entity_profile_name: "PlainSigner" + certificate_authority_name: "ManagementCA" # Directory where the plugins are located # NOTE: plugins have the form of chainloop-plugin- diff --git a/app/controlplane/internal/service/signing.go b/app/controlplane/internal/service/signing.go index e6e9ae995..2faa05171 100644 --- a/app/controlplane/internal/service/signing.go +++ b/app/controlplane/internal/service/signing.go @@ -54,7 +54,7 @@ func (s *SigningService) GenerateSigningCert(ctx context.Context, req *v1.Genera return &v1.GenerateSigningCertResponse{Chain: &v1.CertificateChain{Certificates: certs}}, nil } -func (s *SigningService) GetTrustedRoot(ctx context.Context, req *v1.GetTrustedRootRequest) (*v1.GetTrustedRootResponse, error) { +func (s *SigningService) GetTrustedRoot(ctx context.Context, _ *v1.GetTrustedRootRequest) (*v1.GetTrustedRootResponse, error) { ra := usercontext.CurrentRobotAccount(ctx) if ra == nil { return nil, errors.Unauthorized("missing org", "authentication data is required") diff --git a/app/controlplane/pkg/biz/signing.go b/app/controlplane/pkg/biz/signing.go index 19ab40c31..91aa76aaf 100644 --- a/app/controlplane/pkg/biz/signing.go +++ b/app/controlplane/pkg/biz/signing.go @@ -98,7 +98,7 @@ func (s *SigningUseCase) GetTrustedRoot(ctx context.Context) (*TrustedRoot, erro if err != nil { return nil, fmt.Errorf("getting root chain: %w", err) } - if chain == nil || len(chain) == 0 { + if len(chain) == 0 { continue } keyID := fmt.Sprintf("%x", sha256.Sum256(chain[0].SubjectKeyId)) From 74d06f88960e617ab35b160dbbe047af2d3cb83f Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 19:10:44 +0100 Subject: [PATCH 06/19] comment config change Signed-off-by: Jose I. Paris --- app/controlplane/configs/config.devel.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index bab111c68..175f42f62 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -26,15 +26,15 @@ certificate_authorities: key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} key_pass: chainloop - - ejbca_ca: - server_url: "https://localhost/ejbca" - key_path: "../../devel/devkeys/superadmin.key" - cert_path: "../../devel/devkeys/superadmin.pem" - root_ca_path: "../../../keyfactor/ManagementCA.pem" - - certificate_profile_name: "PlainSigner" - end_entity_profile_name: "PlainSigner" - certificate_authority_name: "ManagementCA" +# - ejbca_ca: +# server_url: "https://localhost/ejbca" +# key_path: "../../devel/devkeys/superadmin.key" +# cert_path: "../../devel/devkeys/superadmin.pem" +# root_ca_path: "../../devel/devkeys/ManagementCA.pem" +# +# certificate_profile_name: "PlainSigner" +# end_entity_profile_name: "PlainSigner" +# certificate_authority_name: "ManagementCA" # Directory where the plugins are located # NOTE: plugins have the form of chainloop-plugin- From 90ef32c681f3f9877b5ec7d5f6f67d81c7f248f1 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 20:27:12 +0100 Subject: [PATCH 07/19] chart compatibility Signed-off-by: Jose I. Paris --- app/controlplane/configs/config.devel.yaml | 3 +-- app/controlplane/pkg/ca/ca.go | 8 +++++- .../templates/controlplane/secret-config.yaml | 26 +++++++++---------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index 175f42f62..919919ede 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -20,8 +20,7 @@ server: # uri: nats://0.0.0.0:4222 certificate_authorities: - - issuer: true - file_ca: + - file_ca: cert_path: ${FILE_CA_CERT_PATH:../../devel/devkeys/ca.pub} key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} key_pass: chainloop diff --git a/app/controlplane/pkg/ca/ca.go b/app/controlplane/pkg/ca/ca.go index 3245c395f..27e2be601 100644 --- a/app/controlplane/pkg/ca/ca.go +++ b/app/controlplane/pkg/ca/ca.go @@ -70,6 +70,11 @@ func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger } } + // If there are more than 1 authority, the `issuer` property must be set + if len(authorities) > 1 && issuerCA == nil { + return nil, fmt.Errorf("at least one issuer CA needs to be configured") + } + return &CertificateAuthorities{ CAs: authorities, // it might be empty SignerCA: issuerCA, @@ -85,7 +90,8 @@ func (c *CertificateAuthorities) GetSignerCA() (CertificateAuthority, error) { return c.SignerCA, nil } - if len(c.CAs) > 0 { + // use as signer if only one is available + if len(c.CAs) == 1 { return c.CAs[0], nil } diff --git a/deployment/chainloop/templates/controlplane/secret-config.yaml b/deployment/chainloop/templates/controlplane/secret-config.yaml index 9b6843cac..3be10e49f 100644 --- a/deployment/chainloop/templates/controlplane/secret-config.yaml +++ b/deployment/chainloop/templates/controlplane/secret-config.yaml @@ -29,24 +29,24 @@ stringData: {{- if and .Values.controlplane.keylessSigning.enabled (eq "fileCA" .Values.controlplane.keylessSigning.backend) }} fileca.secret.yaml: | {{- with .Values.controlplane.keylessSigning.fileCA }} - certificate_authority: - file_ca: - cert_path: "/ca_secrets/file_ca.cert" - key_path: "/ca_secrets/file_ca.key" - key_pass: "{{- required "FileCA keyPass is mandatory" .keyPass }}" + certificate_authorities: + - file_ca: + cert_path: "/ca_secrets/file_ca.cert" + key_path: "/ca_secrets/file_ca.key" + key_pass: "{{- required "FileCA keyPass is mandatory" .keyPass }}" {{- end }} {{- end }} {{- if and .Values.controlplane.keylessSigning.enabled (eq "ejbcaCA" .Values.controlplane.keylessSigning.backend) }} ejbca.secret.yaml: | {{- with .Values.controlplane.keylessSigning.ejbcaCA }} - certificate_authority: - ejbca_ca: - cert_path: "/ca_secrets/ejbca_client.cert" - key_path: "/ca_secrets/ejbca_client.key" - server_url: "{{- required "EJBCA server URL is mandatory" .serverURL }}" - certificate_profile_name: "{{- required "EJBCA certificate profile name is mandatory" .certProfileName }}" - end_entity_profile_name: "{{- required "EJBCA end entity profile name is mandatory" .endEntityProfileName }}" - certificate_authority_name: "{{- required "EJBCA certificate authority name is mandatory" .caName }}" + certificate_authorities: + - ejbca_ca: + cert_path: "/ca_secrets/ejbca_client.cert" + key_path: "/ca_secrets/ejbca_client.key" + server_url: "{{- required "EJBCA server URL is mandatory" .serverURL }}" + certificate_profile_name: "{{- required "EJBCA certificate profile name is mandatory" .certProfileName }}" + end_entity_profile_name: "{{- required "EJBCA end entity profile name is mandatory" .endEntityProfileName }}" + certificate_authority_name: "{{- required "EJBCA certificate authority name is mandatory" .caName }}" {{- end }} {{- end }} config.secret.yaml: | From 4b664471d94e18761a7f97e3dd1e741ffb1f40ad Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 20:52:21 +0100 Subject: [PATCH 08/19] update docs and examples Signed-off-by: Jose I. Paris --- app/controlplane/configs/samples/config.yaml | 29 ++++++++++--------- docs/docs/guides/ejbca/ejbca.md | 30 ++++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/app/controlplane/configs/samples/config.yaml b/app/controlplane/configs/samples/config.yaml index db85a9418..9e0333bf7 100644 --- a/app/controlplane/configs/samples/config.yaml +++ b/app/controlplane/configs/samples/config.yaml @@ -73,22 +73,23 @@ onboarding: - name: "read-write-demo" role: MEMBERSHIP_ROLE_ORG_OWNER -# Certificate authority configuration for key-less scenarios -#certificate_authority: -# file_ca: -# cert_path: "../../devel/devkeys/ca.pub" -# key_path: "../../devel/devkeys/ca.pem" -# key_pass: chainloop +# Certificate authorities configuration for key-less scenarios +#certificate_authorities: +# - file_ca: +# cert_path: "../../devel/devkeys/ca.pub" +# key_path: "../../devel/devkeys/ca.pem" +# key_pass: chainloop +# issuer: true # -# ejbca_ca: -# server_url: "https://localhost/ejbca" -# key_path: "../../devel/devkeys/superadmin.key" -# cert_path: "../../devel/devkeys/superadmin.pem" -# root_ca_path: "../../devel/devkeys/ManagementCA.pem" +# - ejbca_ca: +# server_url: "https://localhost/ejbca" +# key_path: "../../devel/devkeys/superadmin.key" +# cert_path: "../../devel/devkeys/superadmin.pem" +# root_ca_path: "../../devel/devkeys/ManagementCA.pem" # -# certificate_profile_name: "PlainSigner" -# end_entity_profile_name: "PlainSigner" -# certificate_authority_name: "ManagementCA" +# certificate_profile_name: "PlainSigner" +# end_entity_profile_name: "PlainSigner" +# certificate_authority_name: "ManagementCA" # Organizations with Prometheus integration enabled prometheus_integration: diff --git a/docs/docs/guides/ejbca/ejbca.md b/docs/docs/guides/ejbca/ejbca.md index 5d7ba19bf..29d419197 100644 --- a/docs/docs/guides/ejbca/ejbca.md +++ b/docs/docs/guides/ejbca/ejbca.md @@ -20,22 +20,22 @@ Requirements: ### Configure Chainloop to use EJBCA as CA Check `ejbca_cA` section in Chainloop configuration options. In particular, set these values in your [config.yaml](https://github.com/chainloop-dev/chainloop/blob/main/app/controlplane/configs/config.devel.yaml) (these values are also mapped to the [chart values.yaml](https://github.com/chainloop-dev/chainloop/blob/main/deployment/chainloop/values.yaml#L668) file): ```yaml -certificate_authority: - ejbca_ca: - # Where EJBCA service is located - server_url: "https://localhost/ejbca" +certificate_authorities: + - ejbca_ca: + # Where EJBCA service is located + server_url: "https://localhost/ejbca" + + # Cert and private key for Client cert authentication against EJBCA + key_path: "../../devel/devkeys/superadmin.key" + cert_path: "../../devel/devkeys/superadmin.pem" + + # Certificate chain + root_ca_path: "../../devel/devkeys/ManagementCA.pem" - # Cert and private key for Client cert authentication against EJBCA - key_path: "../../devel/devkeys/superadmin.key" - cert_path: "../../devel/devkeys/superadmin.pem" - - # Certificate chain - root_ca_path: "../../devel/devkeys/ManagementCA.pem" - - # EJBCA profile, end entity and CA names - certificate_profile_name: "PlainSigner" - end_entity_profile_name: "PlainSigner" - certificate_authority_name: "ManagementCA" + # EJBCA profile, end entity and CA names + certificate_profile_name: "PlainSigner" + end_entity_profile_name: "PlainSigner" + certificate_authority_name: "ManagementCA" ``` ### Signing Chainloop attestations with EJBCA issued certificates From 1326f8fc54bd0ca1014df25e7dd025fe44bcc0d0 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 22:44:51 +0100 Subject: [PATCH 09/19] verify with trusted root material Signed-off-by: Jose I. Paris --- .../internal/action/workflow_run_describe.go | 101 ++++++++---------- pkg/attestation/verifier/verifier.go | 74 +++++++++++++ 2 files changed, 118 insertions(+), 57 deletions(-) create mode 100644 pkg/attestation/verifier/verifier.go diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index e1335b74d..4c01f072c 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -22,15 +22,14 @@ import ( "errors" "fmt" "sort" + "strings" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" - "github.com/chainloop-dev/chainloop/pkg/attestation" "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" + "github.com/chainloop-dev/chainloop/pkg/attestation/verifier" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" sigs "github.com/sigstore/cosign/v2/pkg/signature" - protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" - bundle2 "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" @@ -159,21 +158,32 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes item.WorkflowRun.FinishedAt = toTimePtr(wr.FinishedAt.AsTime()) } - attestation := resp.GetResult().GetAttestation() + att := resp.GetResult().GetAttestation() // The item does not have associated attestation - if attestation == nil { + if att == nil { return item, nil } - envelope, err := decodeEnvelope(attestation.Envelope) + envelope, err := decodeEnvelope(att.Envelope) if err != nil { return nil, err } - if attestation.Bundle != nil { - if err := verifyBundle(ctx, attestation.Bundle); err != nil { - return nil, err + if att.Bundle != nil { + sc := pb.NewSigningServiceClient(action.cfg.CPConnection) + trResp, err := sc.GetTrustedRoot(ctx, &pb.GetTrustedRootRequest{}) + if err != nil { + return nil, fmt.Errorf("failed getting trusted root: %w", err) + } + + tr, err := trustedRootPbToVerifier(trResp) + if err != nil { + return nil, fmt.Errorf("getting roots: %w", err) } + if err := verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { + return nil, fmt.Errorf("bundle verification failed: %w", err) + } + item.Verified = true } if opts.Verify { @@ -190,31 +200,31 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes return nil, fmt.Errorf("extracting statement: %w", err) } - envVars := make([]*EnvVar, 0, len(attestation.GetEnvVars())) - for _, v := range attestation.GetEnvVars() { + envVars := make([]*EnvVar, 0, len(att.GetEnvVars())) + for _, v := range att.GetEnvVars() { envVars = append(envVars, &EnvVar{Name: v.Name, Value: v.Value}) } - materials := make([]*Material, 0, len(attestation.GetMaterials())) - for _, v := range attestation.GetMaterials() { + materials := make([]*Material, 0, len(att.GetMaterials())) + for _, v := range att.GetMaterials() { materials = append(materials, materialPBToAction(v)) } - keys := make([]string, 0, len(attestation.GetAnnotations())) - for k := range attestation.GetAnnotations() { + keys := make([]string, 0, len(att.GetAnnotations())) + for k := range att.GetAnnotations() { keys = append(keys, k) } sort.Strings(keys) - annotations := make([]*Annotation, 0, len(attestation.GetAnnotations())) + annotations := make([]*Annotation, 0, len(att.GetAnnotations())) for _, k := range keys { annotations = append(annotations, &Annotation{ - Name: k, Value: attestation.GetAnnotations()[k], + Name: k, Value: att.GetAnnotations()[k], }) } evaluations := make(map[string][]*PolicyEvaluation) - for k, v := range attestation.GetPolicyEvaluations() { + for k, v := range att.GetPolicyEvaluations() { evs := make([]*PolicyEvaluation, 0) for _, ev := range v.Evaluations { evs = append(evs, policyEvaluationPBToAction(ev)) @@ -222,7 +232,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes evaluations[k] = evs } - policyEvaluationStatus := attestation.GetPolicyEvaluationStatus() + policyEvaluationStatus := att.GetPolicyEvaluationStatus() item.Attestation = &WorkflowRunAttestationItem{ Envelope: envelope, @@ -230,7 +240,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes EnvVars: envVars, Materials: materials, Annotations: annotations, - Digest: attestation.DigestInCasBackend, + Digest: att.DigestInCasBackend, PolicyEvaluations: evaluations, PolicyEvaluationStatus: &PolicyEvaluationStatus{ Strategy: policyEvaluationStatus.Strategy, @@ -243,6 +253,20 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes return item, nil } +func trustedRootPbToVerifier(resp *pb.GetTrustedRootResponse) (*verifier.TrustedRoot, error) { + tr := &verifier.TrustedRoot{Keys: make(map[string][]*x509.Certificate)} + for k, v := range resp.GetKeys() { + for _, c := range v.Certificates { + cert, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(c)) + if err != nil { + return nil, fmt.Errorf("loading certificate from PEM: %w", err) + } + tr.Keys[k] = append(tr.Keys[k], cert[0]) + } + } + return tr, nil +} + func policyEvaluationPBToAction(in *pb.PolicyEvaluation) *PolicyEvaluation { var pr *PolicyReference if in.PolicyReference != nil { @@ -301,43 +325,6 @@ func materialPBToAction(in *pb.AttestationItem_Material) *Material { return m } -func verifyBundle(ctx context.Context, bundleBytes []byte) error { - chain, err := loadCertificates("devel/devkeys/ca.pub") - if err != nil { - return err - } - var bundle bundle2.Bundle - bundle.Bundle = new(protobundle.Bundle) - // unmarshal and validate - if err := bundle.UnmarshalJSON(bundleBytes); err != nil { - return err - } - pb := bundle.Bundle - if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { - // nothing to verify - return nil - } - - rawCert := pb.GetVerificationMaterial().GetCertificate().GetRawBytes() - signingCert, err := x509.ParseCertificate(rawCert) - if err != nil { - return err - } - - verifier, err := cosign.ValidateAndUnpackCertWithChain(signingCert, chain, &cosign.CheckOpts{IgnoreSCT: true}) - if err != nil { - return fmt.Errorf("validating the certificate: %w", err) - } - - dsseVerifier, err := dsse.NewEnvelopeVerifier(&sigdsee.VerifierAdapter{SignatureVerifier: verifier}) - if err != nil { - return fmt.Errorf("creating DSSE verifier: %w", err) - } - - _, err = dsseVerifier.Verify(ctx, attestation.DSSEEnvelopeFromBundle(pb)) - return err -} - func verifyEnvelope(ctx context.Context, e *dsse.Envelope, opts *WorkflowRunDescribeOpts) error { if opts.PublicKeyRef == "" && opts.CertPath == "" { return fmt.Errorf("no public key or cert path specified") diff --git a/pkg/attestation/verifier/verifier.go b/pkg/attestation/verifier/verifier.go new file mode 100644 index 000000000..8a1d66d53 --- /dev/null +++ b/pkg/attestation/verifier/verifier.go @@ -0,0 +1,74 @@ +// +// Copyright 2025 The Chainloop 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 verifier + +import ( + "context" + "crypto/sha256" + "crypto/x509" + "fmt" + + "github.com/chainloop-dev/chainloop/pkg/attestation" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/cosign/v2/pkg/cosign" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + bundle2 "github.com/sigstore/sigstore-go/pkg/bundle" + sigdsee "github.com/sigstore/sigstore/pkg/signature/dsse" +) + +type TrustedRoot struct { + // map key identifiers to a chain of certificates + Keys map[string][]*x509.Certificate +} + +func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) error { + var bundle bundle2.Bundle + bundle.Bundle = new(protobundle.Bundle) + // unmarshal and validate + if err := bundle.UnmarshalJSON(bundleBytes); err != nil { + return err + } + pb := bundle.Bundle + if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { + // nothing to verify + return nil + } + + rawCert := pb.GetVerificationMaterial().GetCertificate().GetRawBytes() + signingCert, err := x509.ParseCertificate(rawCert) + if err != nil { + return err + } + + aki := fmt.Sprintf("%x", sha256.Sum256(signingCert.AuthorityKeyId)) + chain, ok := tr.Keys[aki] + if !ok { + return fmt.Errorf("trusted root not found for signing key with AKI %s", aki) + } + + verifier, err := cosign.ValidateAndUnpackCertWithChain(signingCert, chain, &cosign.CheckOpts{IgnoreSCT: true}) + if err != nil { + return fmt.Errorf("validating the certificate: %w", err) + } + + dsseVerifier, err := dsse.NewEnvelopeVerifier(&sigdsee.VerifierAdapter{SignatureVerifier: verifier}) + if err != nil { + return fmt.Errorf("creating DSSE verifier: %w", err) + } + + _, err = dsseVerifier.Verify(ctx, attestation.DSSEEnvelopeFromBundle(pb)) + return err +} From 22c6f31bc8a31683e19600290e4afcf4920ebf59 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 22:45:47 +0100 Subject: [PATCH 10/19] configure permissions Signed-off-by: Jose I. Paris --- app/controlplane/internal/server/grpc.go | 2 +- app/controlplane/internal/service/signing.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/controlplane/internal/server/grpc.go b/app/controlplane/internal/server/grpc.go index 92bce4007..f78e5d956 100644 --- a/app/controlplane/internal/server/grpc.go +++ b/app/controlplane/internal/server/grpc.go @@ -254,7 +254,7 @@ func allowListEnabled() selector.MatchFunc { } func requireRobotAccountMatcher() selector.MatchFunc { - const requireMatcher = "controlplane.v1.AttestationService/.*|controlplane.v1.AttestationStateService/.*|controlplane.v1.SigningService/.*" + const requireMatcher = "controlplane.v1.AttestationService/.*|controlplane.v1.AttestationStateService/.*|controlplane.v1.SigningService/GenerateSigningCert" return func(ctx context.Context, operation string) bool { r := regexp.MustCompile(requireMatcher) return r.MatchString(operation) diff --git a/app/controlplane/internal/service/signing.go b/app/controlplane/internal/service/signing.go index 2faa05171..9bc9ac8f6 100644 --- a/app/controlplane/internal/service/signing.go +++ b/app/controlplane/internal/service/signing.go @@ -55,11 +55,6 @@ func (s *SigningService) GenerateSigningCert(ctx context.Context, req *v1.Genera } func (s *SigningService) GetTrustedRoot(ctx context.Context, _ *v1.GetTrustedRootRequest) (*v1.GetTrustedRootResponse, error) { - ra := usercontext.CurrentRobotAccount(ctx) - if ra == nil { - return nil, errors.Unauthorized("missing org", "authentication data is required") - } - tr, err := s.signing.GetTrustedRoot(ctx) if err != nil { return nil, handleUseCaseErr(err, s.log) From 3a72d6cd8b6ebde8a87b35de020578c2c418afc2 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 22:51:10 +0100 Subject: [PATCH 11/19] validate issuer Signed-off-by: Jose I. Paris --- app/controlplane/configs/config.devel.yaml | 3 ++- app/controlplane/pkg/ca/ca.go | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/controlplane/configs/config.devel.yaml b/app/controlplane/configs/config.devel.yaml index 919919ede..175f42f62 100644 --- a/app/controlplane/configs/config.devel.yaml +++ b/app/controlplane/configs/config.devel.yaml @@ -20,7 +20,8 @@ server: # uri: nats://0.0.0.0:4222 certificate_authorities: - - file_ca: + - issuer: true + file_ca: cert_path: ${FILE_CA_CERT_PATH:../../devel/devkeys/ca.pub} key_path: ${FILE_CA_KEY_PATH:../../devel/devkeys/ca.pem} key_pass: chainloop diff --git a/app/controlplane/pkg/ca/ca.go b/app/controlplane/pkg/ca/ca.go index 27e2be601..1e9202bc2 100644 --- a/app/controlplane/pkg/ca/ca.go +++ b/app/controlplane/pkg/ca/ca.go @@ -31,6 +31,7 @@ import ( type CertificateAuthority interface { // CreateCertificateFromCSR accepts a Certificate Request and generates a certificate signed by a signing authority CreateCertificateFromCSR(ctx context.Context, principal identity.Principal, csr *x509.CertificateRequest) (*ca.CodeSigningCertificate, error) + // GetRootChain returns the root certificate chain GetRootChain(ctx context.Context) ([]*x509.Certificate, error) } @@ -75,6 +76,11 @@ func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger return nil, fmt.Errorf("at least one issuer CA needs to be configured") } + // use as signer if only one is available + if len(authorities) == 1 { + issuerCA = authorities[0] + } + return &CertificateAuthorities{ CAs: authorities, // it might be empty SignerCA: issuerCA, @@ -90,10 +96,5 @@ func (c *CertificateAuthorities) GetSignerCA() (CertificateAuthority, error) { return c.SignerCA, nil } - // use as signer if only one is available - if len(c.CAs) == 1 { - return c.CAs[0], nil - } - return nil, fmt.Errorf("no signer CA found") } From 499d14b037c405880ea646e9ff1a05f6148575a5 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 23:00:22 +0100 Subject: [PATCH 12/19] fix tests Signed-off-by: Jose I. Paris --- app/controlplane/pkg/biz/signing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controlplane/pkg/biz/signing_test.go b/app/controlplane/pkg/biz/signing_test.go index 840a201de..df28e2d9e 100644 --- a/app/controlplane/pkg/biz/signing_test.go +++ b/app/controlplane/pkg/biz/signing_test.go @@ -115,7 +115,7 @@ func (s *signingUseCaseTestSuite) SetupTest() { ca, err := NewTestCA() s.Require().NoError(err) - s.uc = &biz.SigningUseCase{CAs: &ca2.CertificateAuthorities{CAs: []ca2.CertificateAuthority{ca}}} + s.uc = &biz.SigningUseCase{CAs: &ca2.CertificateAuthorities{CAs: []ca2.CertificateAuthority{ca}, SignerCA: ca}} } func createCSR() ([]byte, error) { From 41e4d2def5cd00d74afc2514296907672be643db Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 23:03:16 +0100 Subject: [PATCH 13/19] validate only one issuer CA Signed-off-by: Jose I. Paris --- app/controlplane/pkg/ca/ca.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controlplane/pkg/ca/ca.go b/app/controlplane/pkg/ca/ca.go index 1e9202bc2..87673b198 100644 --- a/app/controlplane/pkg/ca/ca.go +++ b/app/controlplane/pkg/ca/ca.go @@ -66,6 +66,10 @@ func NewCertificateAuthoritiesFromConfig(configCAs []*conf.CA, logger log.Logger if authority != nil { authorities = append(authorities, authority) if configCA.Issuer { + if issuerCA != nil { + // already one set, fail + return nil, fmt.Errorf("only one issuer CA can be configured") + } issuerCA = authority } } From 6493981f3f080161219ebc76e4cba95211e4ef7a Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 11 Feb 2025 23:09:29 +0100 Subject: [PATCH 14/19] add issuer: true Signed-off-by: Jose I. Paris --- .../chainloop/templates/controlplane/secret-config.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deployment/chainloop/templates/controlplane/secret-config.yaml b/deployment/chainloop/templates/controlplane/secret-config.yaml index 3be10e49f..255f6ea07 100644 --- a/deployment/chainloop/templates/controlplane/secret-config.yaml +++ b/deployment/chainloop/templates/controlplane/secret-config.yaml @@ -30,7 +30,8 @@ stringData: fileca.secret.yaml: | {{- with .Values.controlplane.keylessSigning.fileCA }} certificate_authorities: - - file_ca: + - issuer: true + file_ca: cert_path: "/ca_secrets/file_ca.cert" key_path: "/ca_secrets/file_ca.key" key_pass: "{{- required "FileCA keyPass is mandatory" .keyPass }}" @@ -40,7 +41,8 @@ stringData: ejbca.secret.yaml: | {{- with .Values.controlplane.keylessSigning.ejbcaCA }} certificate_authorities: - - ejbca_ca: + - issuer: true + ejbca_ca: cert_path: "/ca_secrets/ejbca_client.cert" key_path: "/ca_secrets/ejbca_client.key" server_url: "{{- required "EJBCA server URL is mandatory" .serverURL }}" From 9b1faa5699cfca053594ae75c88156a3a05cc57f Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 12 Feb 2025 08:01:45 +0100 Subject: [PATCH 15/19] fix Signed-off-by: Jose I. Paris --- app/cli/internal/action/workflow_run_describe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index addce851e..b35823f73 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -237,7 +237,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes item.Attestation = &WorkflowRunAttestationItem{ Envelope: envelope, - Bundle: attestation.GetBundle(), + Bundle: att.GetBundle(), statement: statement, EnvVars: envVars, Materials: materials, From 1f0d5ad8d70ad1ad513a19cbb2058b907762904e Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 12 Feb 2025 10:35:51 +0100 Subject: [PATCH 16/19] better errors Signed-off-by: Jose I. Paris --- app/cli/internal/action/workflow_run_describe.go | 10 +++++++--- pkg/attestation/verifier/verifier.go | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index b35823f73..7e38a6419 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -181,10 +181,14 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes if err != nil { return nil, fmt.Errorf("getting roots: %w", err) } - if err := verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { - return nil, fmt.Errorf("bundle verification failed: %w", err) + if err = verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { + if !errors.Is(err, verifier.MissingVerificationMaterialErr) { + action.cfg.Logger.Debug().Err(err).Msg("bundle verification failed") + return nil, errors.New("bundle verification failed") + } + } else { + item.Verified = true } - item.Verified = true } if opts.Verify { diff --git a/pkg/attestation/verifier/verifier.go b/pkg/attestation/verifier/verifier.go index 8a1d66d53..294aa3200 100644 --- a/pkg/attestation/verifier/verifier.go +++ b/pkg/attestation/verifier/verifier.go @@ -19,6 +19,7 @@ import ( "context" "crypto/sha256" "crypto/x509" + "errors" "fmt" "github.com/chainloop-dev/chainloop/pkg/attestation" @@ -34,6 +35,8 @@ type TrustedRoot struct { Keys map[string][]*x509.Certificate } +var MissingVerificationMaterialErr = errors.New("missing material") + func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) error { var bundle bundle2.Bundle bundle.Bundle = new(protobundle.Bundle) @@ -44,13 +47,13 @@ func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) erro pb := bundle.Bundle if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { // nothing to verify - return nil + return MissingVerificationMaterialErr } rawCert := pb.GetVerificationMaterial().GetCertificate().GetRawBytes() signingCert, err := x509.ParseCertificate(rawCert) if err != nil { - return err + return fmt.Errorf("could not parse certificate from bundle: %w", err) } aki := fmt.Sprintf("%x", sha256.Sum256(signingCert.AuthorityKeyId)) From a90b9b7a276459edf287aa9942c8e92ed4317ecc Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 12 Feb 2025 10:39:54 +0100 Subject: [PATCH 17/19] better errors Signed-off-by: Jose I. Paris --- pkg/attestation/verifier/verifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/attestation/verifier/verifier.go b/pkg/attestation/verifier/verifier.go index 294aa3200..73d817e4f 100644 --- a/pkg/attestation/verifier/verifier.go +++ b/pkg/attestation/verifier/verifier.go @@ -42,7 +42,7 @@ func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) erro bundle.Bundle = new(protobundle.Bundle) // unmarshal and validate if err := bundle.UnmarshalJSON(bundleBytes); err != nil { - return err + return fmt.Errorf("invalid bundle: %w", err) } pb := bundle.Bundle if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { From 1b14aaccd4e8810347f41d53bdf62451d2492130 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 12 Feb 2025 11:09:29 +0100 Subject: [PATCH 18/19] lint Signed-off-by: Jose I. Paris --- .../internal/action/workflow_run_describe.go | 2 +- pkg/attestation/verifier/verifier.go | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index 7e38a6419..d48613b78 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -182,7 +182,7 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes return nil, fmt.Errorf("getting roots: %w", err) } if err = verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { - if !errors.Is(err, verifier.MissingVerificationMaterialErr) { + if !errors.Is(err, verifier.ErrMissingVerificationMaterial) { action.cfg.Logger.Debug().Err(err).Msg("bundle verification failed") return nil, errors.New("bundle verification failed") } diff --git a/pkg/attestation/verifier/verifier.go b/pkg/attestation/verifier/verifier.go index 73d817e4f..d8942a90d 100644 --- a/pkg/attestation/verifier/verifier.go +++ b/pkg/attestation/verifier/verifier.go @@ -26,8 +26,9 @@ import ( "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/pkg/cosign" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" - bundle2 "github.com/sigstore/sigstore-go/pkg/bundle" + sigstorebundle "github.com/sigstore/sigstore-go/pkg/bundle" sigdsee "github.com/sigstore/sigstore/pkg/signature/dsse" + "google.golang.org/protobuf/encoding/protojson" ) type TrustedRoot struct { @@ -35,26 +36,31 @@ type TrustedRoot struct { Keys map[string][]*x509.Certificate } -var MissingVerificationMaterialErr = errors.New("missing material") +var ErrMissingVerificationMaterial = errors.New("missing material") func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) error { - var bundle bundle2.Bundle - bundle.Bundle = new(protobundle.Bundle) + bundle := new(protobundle.Bundle) // unmarshal and validate - if err := bundle.UnmarshalJSON(bundleBytes); err != nil { + if err := protojson.Unmarshal(bundleBytes, bundle); err != nil { return fmt.Errorf("invalid bundle: %w", err) } - pb := bundle.Bundle - if pb.GetVerificationMaterial() == nil || pb.GetVerificationMaterial().GetCertificate() == nil { + + if bundle.GetVerificationMaterial() == nil || bundle.GetVerificationMaterial().GetCertificate() == nil { // nothing to verify - return MissingVerificationMaterialErr + return ErrMissingVerificationMaterial + } + + // Use sigstore helpers + var sb sigstorebundle.Bundle + if err := sb.UnmarshalJSON(bundleBytes); err != nil { + return fmt.Errorf("invalid bundle: %w", err) } - rawCert := pb.GetVerificationMaterial().GetCertificate().GetRawBytes() - signingCert, err := x509.ParseCertificate(rawCert) + vc, err := sb.VerificationContent() if err != nil { - return fmt.Errorf("could not parse certificate from bundle: %w", err) + return fmt.Errorf("could not get verification material: %w", err) } + signingCert := vc.GetCertificate() aki := fmt.Sprintf("%x", sha256.Sum256(signingCert.AuthorityKeyId)) chain, ok := tr.Keys[aki] @@ -72,6 +78,6 @@ func VerifyBundle(ctx context.Context, bundleBytes []byte, tr *TrustedRoot) erro return fmt.Errorf("creating DSSE verifier: %w", err) } - _, err = dsseVerifier.Verify(ctx, attestation.DSSEEnvelopeFromBundle(pb)) + _, err = dsseVerifier.Verify(ctx, attestation.DSSEEnvelopeFromBundle(bundle)) return err } From 7402b3ba36adb5c8bfe76d8cc3c5037617b853d0 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Wed, 12 Feb 2025 11:38:54 +0100 Subject: [PATCH 19/19] support non keyless scenarios Signed-off-by: Jose I. Paris --- .../internal/action/workflow_run_describe.go | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/app/cli/internal/action/workflow_run_describe.go b/app/cli/internal/action/workflow_run_describe.go index d48613b78..ffb2e3f6e 100644 --- a/app/cli/internal/action/workflow_run_describe.go +++ b/app/cli/internal/action/workflow_run_describe.go @@ -27,15 +27,16 @@ import ( pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop" "github.com/chainloop-dev/chainloop/pkg/attestation/verifier" + intoto "github.com/in-toto/attestation/go/v1" + "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" - - intoto "github.com/in-toto/attestation/go/v1" - "github.com/secure-systems-lab/go-securesystemslib/dsse" sigdsee "github.com/sigstore/sigstore/pkg/signature/dsse" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type WorkflowRunDescribe struct { @@ -174,20 +175,25 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes sc := pb.NewSigningServiceClient(action.cfg.CPConnection) trResp, err := sc.GetTrustedRoot(ctx, &pb.GetTrustedRootRequest{}) if err != nil { - return nil, fmt.Errorf("failed getting trusted root: %w", err) + // if trusted root is not implemented, skip verification + if status.Code(err) != codes.Unimplemented { + return nil, fmt.Errorf("failed getting trusted root: %w", err) + } } - tr, err := trustedRootPbToVerifier(trResp) - if err != nil { - return nil, fmt.Errorf("getting roots: %w", err) - } - if err = verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { - if !errors.Is(err, verifier.ErrMissingVerificationMaterial) { - action.cfg.Logger.Debug().Err(err).Msg("bundle verification failed") - return nil, errors.New("bundle verification failed") + if trResp != nil { + tr, err := trustedRootPbToVerifier(trResp) + if err != nil { + return nil, fmt.Errorf("getting roots: %w", err) + } + if err = verifier.VerifyBundle(ctx, att.Bundle, tr); err != nil { + if !errors.Is(err, verifier.ErrMissingVerificationMaterial) { + action.cfg.Logger.Debug().Err(err).Msg("bundle verification failed") + return nil, errors.New("bundle verification failed") + } + } else { + item.Verified = true } - } else { - item.Verified = true } }