From cc1ac2e255a706a3434dfac972b6823c4503954b Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 15 Mar 2026 09:07:12 -0700 Subject: [PATCH] Separate web UI into standalone metacrypt-web binary The vault server holds in-memory unsealed state (KEK, engine keys) that is lost on restart, requiring a full unseal ceremony. Previously the web UI ran inside the vault process, so any UI change forced a restart and re-unseal. This change extracts the web UI into a separate metacrypt-web binary that communicates with the vault over an authenticated gRPC connection. The web server carries no sealed state and can be restarted freely. - gen/metacrypt/v1/: generated Go bindings from proto/metacrypt/v1/ - internal/grpcserver/: full gRPC server implementation (System, Auth, Engine, PKI, Policy, ACME services) with seal/auth/admin interceptors - internal/webserver/: web server with gRPC vault client; templates embedded via web/embed.go (no runtime web/ directory needed) - cmd/metacrypt-web/: standalone binary entry point - internal/config: added [web] section (listen_addr, vault_grpc, etc.) - internal/server/routes.go: removed all web UI routes and handlers - cmd/metacrypt/server.go: starts gRPC server alongside HTTP server - Deploy: Dockerfile builds both binaries, docker-compose adds metacrypt-web service, new metacrypt-web.service systemd unit, Makefile gains proto/metacrypt-web targets Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 3 +- Makefile | 23 +- cmd/metacrypt-web/main.go | 75 +++ cmd/metacrypt/server.go | 10 + deploy/docker/docker-compose.yml | 19 +- deploy/systemd/metacrypt-web.service | 46 ++ gen/metacrypt/v1/acme.pb.go | 670 +++++++++++++++++++++++++++ gen/metacrypt/v1/acme_grpc.pb.go | 259 +++++++++++ gen/metacrypt/v1/auth.pb.go | 386 +++++++++++++++ gen/metacrypt/v1/auth_grpc.pb.go | 197 ++++++++ gen/metacrypt/v1/common.pb.go | 59 +++ gen/metacrypt/v1/engine.pb.go | 555 ++++++++++++++++++++++ gen/metacrypt/v1/engine_grpc.pb.go | 235 ++++++++++ gen/metacrypt/v1/pki.pb.go | 386 +++++++++++++++ gen/metacrypt/v1/pki_grpc.pb.go | 203 ++++++++ gen/metacrypt/v1/policy.pb.go | 456 ++++++++++++++++++ gen/metacrypt/v1/policy_grpc.pb.go | 235 ++++++++++ gen/metacrypt/v1/system.pb.go | 287 +++++++----- gen/metacrypt/v1/system_grpc.pb.go | 63 ++- go.mod | 8 +- go.sum | 9 + internal/config/config.go | 20 + internal/grpcserver/acme.go | 130 ++++++ internal/grpcserver/auth.go | 56 +++ internal/grpcserver/engine.go | 112 +++++ internal/grpcserver/interceptors.go | 107 +++++ internal/grpcserver/pki.go | 81 ++++ internal/grpcserver/policy.go | 86 ++++ internal/grpcserver/server.go | 162 +++++++ internal/grpcserver/system.go | 80 ++++ internal/server/routes.go | 499 +------------------- internal/server/server_test.go | 11 +- internal/webserver/client.go | 199 ++++++++ internal/webserver/routes.go | 430 +++++++++++++++++ internal/webserver/server.go | 112 +++++ internal/webserver/util.go | 37 ++ web/embed.go | 9 + 37 files changed, 5668 insertions(+), 647 deletions(-) create mode 100644 cmd/metacrypt-web/main.go create mode 100644 deploy/systemd/metacrypt-web.service create mode 100644 gen/metacrypt/v1/acme.pb.go create mode 100644 gen/metacrypt/v1/acme_grpc.pb.go create mode 100644 gen/metacrypt/v1/auth.pb.go create mode 100644 gen/metacrypt/v1/auth_grpc.pb.go create mode 100644 gen/metacrypt/v1/common.pb.go create mode 100644 gen/metacrypt/v1/engine.pb.go create mode 100644 gen/metacrypt/v1/engine_grpc.pb.go create mode 100644 gen/metacrypt/v1/pki.pb.go create mode 100644 gen/metacrypt/v1/pki_grpc.pb.go create mode 100644 gen/metacrypt/v1/policy.pb.go create mode 100644 gen/metacrypt/v1/policy_grpc.pb.go create mode 100644 internal/grpcserver/acme.go create mode 100644 internal/grpcserver/auth.go create mode 100644 internal/grpcserver/engine.go create mode 100644 internal/grpcserver/interceptors.go create mode 100644 internal/grpcserver/pki.go create mode 100644 internal/grpcserver/policy.go create mode 100644 internal/grpcserver/server.go create mode 100644 internal/grpcserver/system.go create mode 100644 internal/webserver/client.go create mode 100644 internal/webserver/routes.go create mode 100644 internal/webserver/server.go create mode 100644 internal/webserver/util.go create mode 100644 web/embed.go diff --git a/Dockerfile b/Dockerfile index dd24ab4..741fcf4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /metacrypt ./cmd/metacrypt +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /metacrypt-web ./cmd/metacrypt-web FROM alpine:3.21 @@ -17,7 +18,7 @@ RUN apk add --no-cache ca-certificates tzdata \ && mkdir -p /srv/metacrypt && chown metacrypt:metacrypt /srv/metacrypt COPY --from=builder /metacrypt /usr/local/bin/metacrypt -COPY web/ /srv/metacrypt/web/ +COPY --from=builder /metacrypt-web /usr/local/bin/metacrypt-web # /srv/metacrypt is the single volume mount point. # It must contain: diff --git a/Makefile b/Makefile index ffc8db7..bc14b0a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,17 @@ -.PHONY: build test vet clean docker all devserver metacrypt +.PHONY: build test vet clean docker all devserver metacrypt metacrypt-web proto + +LDFLAGS := -trimpath -ldflags="-s -w -X main.version=$(shell git describe --tags --always --dirty 2>/dev/null || echo dev)" + +proto: + protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + proto/metacrypt/v1/*.proto metacrypt: - go build -trimpath -ldflags="-s -w -X main.version=$(shell git describe --tags --always --dirty 2>/dev/null || echo dev)" -o metacrypt ./cmd/metacrypt + go build $(LDFLAGS) -o metacrypt ./cmd/metacrypt + +metacrypt-web: + go build $(LDFLAGS) -o metacrypt-web ./cmd/metacrypt-web build: go build ./... @@ -13,7 +23,7 @@ vet: go vet ./... clean: - rm -f metacrypt + rm -f metacrypt metacrypt-web docker: docker build -t metacrypt . @@ -21,7 +31,8 @@ docker: docker-compose: docker compose -f deploy/docker/docker-compose.yml up --build -devserver: metacrypt - ./metacrypt server --config srv/metacrypt.toml +devserver: metacrypt metacrypt-web + ./metacrypt server --config srv/metacrypt.toml & + ./metacrypt-web --config srv/metacrypt.toml -all: vet test metacrypt +all: vet test metacrypt metacrypt-web diff --git a/cmd/metacrypt-web/main.go b/cmd/metacrypt-web/main.go new file mode 100644 index 0000000..7d3a3f8 --- /dev/null +++ b/cmd/metacrypt-web/main.go @@ -0,0 +1,75 @@ +// metacrypt-web is the standalone web UI server for Metacrypt. +// It communicates with the vault over gRPC and can be restarted independently +// without requiring the vault to be re-unsealed. +package main + +import ( + "context" + "fmt" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + + "git.wntrmute.dev/kyle/metacrypt/internal/config" + "git.wntrmute.dev/kyle/metacrypt/internal/webserver" +) + +var cfgFile string + +var rootCmd = &cobra.Command{ + Use: "metacrypt-web", + Short: "Metacrypt web UI server", + Long: "Standalone web UI server for Metacrypt. Communicates with the vault over gRPC.", + RunE: run, +} + +func init() { + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default /srv/metacrypt/metacrypt.toml)") +} + +func run(cmd *cobra.Command, args []string) error { + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + configPath := cfgFile + if configPath == "" { + configPath = "/srv/metacrypt/metacrypt.toml" + } + + cfg, err := config.Load(configPath) + if err != nil { + return err + } + + if cfg.Web.VaultGRPC == "" { + return fmt.Errorf("web.vault_grpc is required in config") + } + + ws, err := webserver.New(cfg, logger) + if err != nil { + return err + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + go func() { + if err := ws.Start(); err != nil { + logger.Error("web server error", "error", err) + os.Exit(1) + } + }() + + <-ctx.Done() + logger.Info("shutting down web server") + return ws.Shutdown(context.Background()) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/metacrypt/server.go b/cmd/metacrypt/server.go index 7ba9dba..c8f98ef 100644 --- a/cmd/metacrypt/server.go +++ b/cmd/metacrypt/server.go @@ -16,6 +16,7 @@ import ( "git.wntrmute.dev/kyle/metacrypt/internal/db" "git.wntrmute.dev/kyle/metacrypt/internal/engine" "git.wntrmute.dev/kyle/metacrypt/internal/engine/ca" + "git.wntrmute.dev/kyle/metacrypt/internal/grpcserver" "git.wntrmute.dev/kyle/metacrypt/internal/policy" "git.wntrmute.dev/kyle/metacrypt/internal/seal" "git.wntrmute.dev/kyle/metacrypt/internal/server" @@ -75,10 +76,18 @@ func runServer(cmd *cobra.Command, args []string) error { engineRegistry.RegisterFactory(engine.EngineTypeCA, ca.NewCAEngine) srv := server.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger, version) + grpcSrv := grpcserver.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() + go func() { + if err := grpcSrv.Start(); err != nil { + logger.Error("gRPC server error", "error", err) + os.Exit(1) + } + }() + go func() { if err := srv.Start(); err != nil { logger.Error("server error", "error", err) @@ -97,6 +106,7 @@ func runServer(cmd *cobra.Command, args []string) error { <-ctx.Done() logger.Info("shutting down") + grpcSrv.Shutdown() srv.ShutdownGRPC() return srv.Shutdown(context.Background()) } diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index be2a5b4..ea4b5b7 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -7,12 +7,9 @@ services: restart: unless-stopped ports: - "8443:8443" + - "9443:9443" volumes: - metacrypt-data:/srv/metacrypt - # To populate /srv/metacrypt before first run, use an init container or - # bind-mount a host directory instead of a named volume: - # volumes: - # - ./data:/srv/metacrypt healthcheck: test: ["CMD", "metacrypt", "status", "--addr", "https://localhost:8443", "--ca-cert", "/srv/metacrypt/certs/ca.crt"] interval: 30s @@ -20,5 +17,19 @@ services: retries: 3 start_period: 10s + metacrypt-web: + build: + context: ../.. + dockerfile: Dockerfile + container_name: metacrypt-web + command: ["/usr/local/bin/metacrypt-web", "--config", "/srv/metacrypt/metacrypt.toml"] + restart: unless-stopped + ports: + - "8080:8080" + volumes: + - metacrypt-data:/srv/metacrypt + depends_on: + - metacrypt + volumes: metacrypt-data: diff --git a/deploy/systemd/metacrypt-web.service b/deploy/systemd/metacrypt-web.service new file mode 100644 index 0000000..ed7e2b1 --- /dev/null +++ b/deploy/systemd/metacrypt-web.service @@ -0,0 +1,46 @@ +[Unit] +Description=Metacrypt web UI server +Documentation=https://git.wntrmute.dev/kyle/metacrypt +After=network-online.target metacrypt.service +Wants=network-online.target +Requires=metacrypt.service + +[Service] +Type=simple +User=metacrypt +Group=metacrypt + +ExecStart=/usr/local/bin/metacrypt-web --config /srv/metacrypt/metacrypt.toml +ExecReload=/bin/kill -HUP $MAINPID + +Restart=on-failure +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=true +RestrictRealtime=true + +# Allow read access to config and certs +ReadOnlyPaths=/srv/metacrypt + +# Limit file descriptor count +LimitNOFILE=65535 + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=metacrypt-web + +[Install] +WantedBy=multi-user.target diff --git a/gen/metacrypt/v1/acme.pb.go b/gen/metacrypt/v1/acme.pb.go new file mode 100644 index 0000000..1fee433 --- /dev/null +++ b/gen/metacrypt/v1/acme.pb.go @@ -0,0 +1,670 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/acme.proto + +package metacryptv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateEABRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateEABRequest) Reset() { + *x = CreateEABRequest{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateEABRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateEABRequest) ProtoMessage() {} + +func (x *CreateEABRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateEABRequest.ProtoReflect.Descriptor instead. +func (*CreateEABRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateEABRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +type CreateEABResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // kid is the key identifier to pass to the ACME client. + Kid string `protobuf:"bytes,1,opt,name=kid,proto3" json:"kid,omitempty"` + // hmac_key is the raw 32-byte HMAC-SHA256 key. + // Base64url-encode this value when configuring an ACME client. + HmacKey []byte `protobuf:"bytes,2,opt,name=hmac_key,json=hmacKey,proto3" json:"hmac_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateEABResponse) Reset() { + *x = CreateEABResponse{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateEABResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateEABResponse) ProtoMessage() {} + +func (x *CreateEABResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateEABResponse.ProtoReflect.Descriptor instead. +func (*CreateEABResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateEABResponse) GetKid() string { + if x != nil { + return x.Kid + } + return "" +} + +func (x *CreateEABResponse) GetHmacKey() []byte { + if x != nil { + return x.HmacKey + } + return nil +} + +type SetACMEConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + // default_issuer is the name of the CA issuer to use for ACME certificates. + // The issuer must already exist on the CA mount. + DefaultIssuer string `protobuf:"bytes,2,opt,name=default_issuer,json=defaultIssuer,proto3" json:"default_issuer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetACMEConfigRequest) Reset() { + *x = SetACMEConfigRequest{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetACMEConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetACMEConfigRequest) ProtoMessage() {} + +func (x *SetACMEConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetACMEConfigRequest.ProtoReflect.Descriptor instead. +func (*SetACMEConfigRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{2} +} + +func (x *SetACMEConfigRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *SetACMEConfigRequest) GetDefaultIssuer() string { + if x != nil { + return x.DefaultIssuer + } + return "" +} + +type SetACMEConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetACMEConfigResponse) Reset() { + *x = SetACMEConfigResponse{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetACMEConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetACMEConfigResponse) ProtoMessage() {} + +func (x *SetACMEConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetACMEConfigResponse.ProtoReflect.Descriptor instead. +func (*SetACMEConfigResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{3} +} + +func (x *SetACMEConfigResponse) GetOk() bool { + if x != nil { + return x.Ok + } + return false +} + +type ListACMEAccountsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListACMEAccountsRequest) Reset() { + *x = ListACMEAccountsRequest{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListACMEAccountsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACMEAccountsRequest) ProtoMessage() {} + +func (x *ListACMEAccountsRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACMEAccountsRequest.ProtoReflect.Descriptor instead. +func (*ListACMEAccountsRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{4} +} + +func (x *ListACMEAccountsRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +type ListACMEAccountsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accounts []*ACMEAccount `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListACMEAccountsResponse) Reset() { + *x = ListACMEAccountsResponse{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListACMEAccountsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACMEAccountsResponse) ProtoMessage() {} + +func (x *ListACMEAccountsResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACMEAccountsResponse.ProtoReflect.Descriptor instead. +func (*ListACMEAccountsResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{5} +} + +func (x *ListACMEAccountsResponse) GetAccounts() []*ACMEAccount { + if x != nil { + return x.Accounts + } + return nil +} + +type ACMEAccount struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + Contact []string `protobuf:"bytes,3,rep,name=contact,proto3" json:"contact,omitempty"` + MciasUsername string `protobuf:"bytes,4,opt,name=mcias_username,json=mciasUsername,proto3" json:"mcias_username,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ACMEAccount) Reset() { + *x = ACMEAccount{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ACMEAccount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ACMEAccount) ProtoMessage() {} + +func (x *ACMEAccount) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ACMEAccount.ProtoReflect.Descriptor instead. +func (*ACMEAccount) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{6} +} + +func (x *ACMEAccount) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ACMEAccount) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ACMEAccount) GetContact() []string { + if x != nil { + return x.Contact + } + return nil +} + +func (x *ACMEAccount) GetMciasUsername() string { + if x != nil { + return x.MciasUsername + } + return "" +} + +func (x *ACMEAccount) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type ListACMEOrdersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListACMEOrdersRequest) Reset() { + *x = ListACMEOrdersRequest{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListACMEOrdersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACMEOrdersRequest) ProtoMessage() {} + +func (x *ListACMEOrdersRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACMEOrdersRequest.ProtoReflect.Descriptor instead. +func (*ListACMEOrdersRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{7} +} + +func (x *ListACMEOrdersRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +type ListACMEOrdersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Orders []*ACMEOrder `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListACMEOrdersResponse) Reset() { + *x = ListACMEOrdersResponse{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListACMEOrdersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListACMEOrdersResponse) ProtoMessage() {} + +func (x *ListACMEOrdersResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListACMEOrdersResponse.ProtoReflect.Descriptor instead. +func (*ListACMEOrdersResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{8} +} + +func (x *ListACMEOrdersResponse) GetOrders() []*ACMEOrder { + if x != nil { + return x.Orders + } + return nil +} + +type ACMEOrder struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + // identifiers are in "type:value" format, e.g. "dns:example.com". + Identifiers []string `protobuf:"bytes,4,rep,name=identifiers,proto3" json:"identifiers,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt string `protobuf:"bytes,6,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ACMEOrder) Reset() { + *x = ACMEOrder{} + mi := &file_metacrypt_v1_acme_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ACMEOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ACMEOrder) ProtoMessage() {} + +func (x *ACMEOrder) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_acme_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ACMEOrder.ProtoReflect.Descriptor instead. +func (*ACMEOrder) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_acme_proto_rawDescGZIP(), []int{9} +} + +func (x *ACMEOrder) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ACMEOrder) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +func (x *ACMEOrder) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ACMEOrder) GetIdentifiers() []string { + if x != nil { + return x.Identifiers + } + return nil +} + +func (x *ACMEOrder) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *ACMEOrder) GetExpiresAt() string { + if x != nil { + return x.ExpiresAt + } + return "" +} + +var File_metacrypt_v1_acme_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_acme_proto_rawDesc = "" + + "\n" + + "\x17metacrypt/v1/acme.proto\x12\fmetacrypt.v1\"(\n" + + "\x10CreateEABRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\"@\n" + + "\x11CreateEABResponse\x12\x10\n" + + "\x03kid\x18\x01 \x01(\tR\x03kid\x12\x19\n" + + "\bhmac_key\x18\x02 \x01(\fR\ahmacKey\"S\n" + + "\x14SetACMEConfigRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12%\n" + + "\x0edefault_issuer\x18\x02 \x01(\tR\rdefaultIssuer\"'\n" + + "\x15SetACMEConfigResponse\x12\x0e\n" + + "\x02ok\x18\x01 \x01(\bR\x02ok\"/\n" + + "\x17ListACMEAccountsRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\"Q\n" + + "\x18ListACMEAccountsResponse\x125\n" + + "\baccounts\x18\x01 \x03(\v2\x19.metacrypt.v1.ACMEAccountR\baccounts\"\x95\x01\n" + + "\vACMEAccount\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x18\n" + + "\acontact\x18\x03 \x03(\tR\acontact\x12%\n" + + "\x0emcias_username\x18\x04 \x01(\tR\rmciasUsername\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\"-\n" + + "\x15ListACMEOrdersRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\"I\n" + + "\x16ListACMEOrdersResponse\x12/\n" + + "\x06orders\x18\x01 \x03(\v2\x17.metacrypt.v1.ACMEOrderR\x06orders\"\xb2\x01\n" + + "\tACMEOrder\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "account_id\x18\x02 \x01(\tR\taccountId\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12 \n" + + "\videntifiers\x18\x04 \x03(\tR\videntifiers\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\x12\x1d\n" + + "\n" + + "expires_at\x18\x06 \x01(\tR\texpiresAt2\xe9\x02\n" + + "\vACMEService\x12L\n" + + "\tCreateEAB\x12\x1e.metacrypt.v1.CreateEABRequest\x1a\x1f.metacrypt.v1.CreateEABResponse\x12T\n" + + "\tSetConfig\x12\".metacrypt.v1.SetACMEConfigRequest\x1a#.metacrypt.v1.SetACMEConfigResponse\x12]\n" + + "\fListAccounts\x12%.metacrypt.v1.ListACMEAccountsRequest\x1a&.metacrypt.v1.ListACMEAccountsResponse\x12W\n" + + "\n" + + "ListOrders\x12#.metacrypt.v1.ListACMEOrdersRequest\x1a$.metacrypt.v1.ListACMEOrdersResponseB>Z metacrypt.v1.ACMEAccount + 9, // 1: metacrypt.v1.ListACMEOrdersResponse.orders:type_name -> metacrypt.v1.ACMEOrder + 0, // 2: metacrypt.v1.ACMEService.CreateEAB:input_type -> metacrypt.v1.CreateEABRequest + 2, // 3: metacrypt.v1.ACMEService.SetConfig:input_type -> metacrypt.v1.SetACMEConfigRequest + 4, // 4: metacrypt.v1.ACMEService.ListAccounts:input_type -> metacrypt.v1.ListACMEAccountsRequest + 7, // 5: metacrypt.v1.ACMEService.ListOrders:input_type -> metacrypt.v1.ListACMEOrdersRequest + 1, // 6: metacrypt.v1.ACMEService.CreateEAB:output_type -> metacrypt.v1.CreateEABResponse + 3, // 7: metacrypt.v1.ACMEService.SetConfig:output_type -> metacrypt.v1.SetACMEConfigResponse + 5, // 8: metacrypt.v1.ACMEService.ListAccounts:output_type -> metacrypt.v1.ListACMEAccountsResponse + 8, // 9: metacrypt.v1.ACMEService.ListOrders:output_type -> metacrypt.v1.ListACMEOrdersResponse + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_metacrypt_v1_acme_proto_init() } +func file_metacrypt_v1_acme_proto_init() { + if File_metacrypt_v1_acme_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_acme_proto_rawDesc), len(file_metacrypt_v1_acme_proto_rawDesc)), + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_metacrypt_v1_acme_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_acme_proto_depIdxs, + MessageInfos: file_metacrypt_v1_acme_proto_msgTypes, + }.Build() + File_metacrypt_v1_acme_proto = out.File + file_metacrypt_v1_acme_proto_goTypes = nil + file_metacrypt_v1_acme_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v1/acme_grpc.pb.go b/gen/metacrypt/v1/acme_grpc.pb.go new file mode 100644 index 0000000..d1c50f6 --- /dev/null +++ b/gen/metacrypt/v1/acme_grpc.pb.go @@ -0,0 +1,259 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/acme.proto + +package metacryptv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + ACMEService_CreateEAB_FullMethodName = "/metacrypt.v1.ACMEService/CreateEAB" + ACMEService_SetConfig_FullMethodName = "/metacrypt.v1.ACMEService/SetConfig" + ACMEService_ListAccounts_FullMethodName = "/metacrypt.v1.ACMEService/ListAccounts" + ACMEService_ListOrders_FullMethodName = "/metacrypt.v1.ACMEService/ListOrders" +) + +// ACMEServiceClient is the client API for ACMEService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// ACMEService provides authenticated management of ACME state. +// These RPCs correspond to the REST management endpoints at /v1/acme/{mount}/. +// The ACME protocol endpoints themselves (/acme/{mount}/...) are HTTP-only +// per RFC 8555 and have no gRPC equivalents. +type ACMEServiceClient interface { + // CreateEAB creates External Account Binding credentials for the + // authenticated MCIAS user. The returned kid and hmac_key are used + // with any RFC 8555-compliant ACME client to register an account. + CreateEAB(ctx context.Context, in *CreateEABRequest, opts ...grpc.CallOption) (*CreateEABResponse, error) + // SetConfig sets the ACME configuration for a CA mount. + // Currently configures the default issuer used for ACME certificate issuance. + SetConfig(ctx context.Context, in *SetACMEConfigRequest, opts ...grpc.CallOption) (*SetACMEConfigResponse, error) + // ListAccounts returns all ACME accounts for a CA mount. Admin only. + ListAccounts(ctx context.Context, in *ListACMEAccountsRequest, opts ...grpc.CallOption) (*ListACMEAccountsResponse, error) + // ListOrders returns all ACME orders for a CA mount. Admin only. + ListOrders(ctx context.Context, in *ListACMEOrdersRequest, opts ...grpc.CallOption) (*ListACMEOrdersResponse, error) +} + +type aCMEServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewACMEServiceClient(cc grpc.ClientConnInterface) ACMEServiceClient { + return &aCMEServiceClient{cc} +} + +func (c *aCMEServiceClient) CreateEAB(ctx context.Context, in *CreateEABRequest, opts ...grpc.CallOption) (*CreateEABResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateEABResponse) + err := c.cc.Invoke(ctx, ACMEService_CreateEAB_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aCMEServiceClient) SetConfig(ctx context.Context, in *SetACMEConfigRequest, opts ...grpc.CallOption) (*SetACMEConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetACMEConfigResponse) + err := c.cc.Invoke(ctx, ACMEService_SetConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aCMEServiceClient) ListAccounts(ctx context.Context, in *ListACMEAccountsRequest, opts ...grpc.CallOption) (*ListACMEAccountsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListACMEAccountsResponse) + err := c.cc.Invoke(ctx, ACMEService_ListAccounts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *aCMEServiceClient) ListOrders(ctx context.Context, in *ListACMEOrdersRequest, opts ...grpc.CallOption) (*ListACMEOrdersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListACMEOrdersResponse) + err := c.cc.Invoke(ctx, ACMEService_ListOrders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ACMEServiceServer is the server API for ACMEService service. +// All implementations must embed UnimplementedACMEServiceServer +// for forward compatibility. +// +// ACMEService provides authenticated management of ACME state. +// These RPCs correspond to the REST management endpoints at /v1/acme/{mount}/. +// The ACME protocol endpoints themselves (/acme/{mount}/...) are HTTP-only +// per RFC 8555 and have no gRPC equivalents. +type ACMEServiceServer interface { + // CreateEAB creates External Account Binding credentials for the + // authenticated MCIAS user. The returned kid and hmac_key are used + // with any RFC 8555-compliant ACME client to register an account. + CreateEAB(context.Context, *CreateEABRequest) (*CreateEABResponse, error) + // SetConfig sets the ACME configuration for a CA mount. + // Currently configures the default issuer used for ACME certificate issuance. + SetConfig(context.Context, *SetACMEConfigRequest) (*SetACMEConfigResponse, error) + // ListAccounts returns all ACME accounts for a CA mount. Admin only. + ListAccounts(context.Context, *ListACMEAccountsRequest) (*ListACMEAccountsResponse, error) + // ListOrders returns all ACME orders for a CA mount. Admin only. + ListOrders(context.Context, *ListACMEOrdersRequest) (*ListACMEOrdersResponse, error) + mustEmbedUnimplementedACMEServiceServer() +} + +// UnimplementedACMEServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedACMEServiceServer struct{} + +func (UnimplementedACMEServiceServer) CreateEAB(context.Context, *CreateEABRequest) (*CreateEABResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateEAB not implemented") +} +func (UnimplementedACMEServiceServer) SetConfig(context.Context, *SetACMEConfigRequest) (*SetACMEConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetConfig not implemented") +} +func (UnimplementedACMEServiceServer) ListAccounts(context.Context, *ListACMEAccountsRequest) (*ListACMEAccountsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListAccounts not implemented") +} +func (UnimplementedACMEServiceServer) ListOrders(context.Context, *ListACMEOrdersRequest) (*ListACMEOrdersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListOrders not implemented") +} +func (UnimplementedACMEServiceServer) mustEmbedUnimplementedACMEServiceServer() {} +func (UnimplementedACMEServiceServer) testEmbeddedByValue() {} + +// UnsafeACMEServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ACMEServiceServer will +// result in compilation errors. +type UnsafeACMEServiceServer interface { + mustEmbedUnimplementedACMEServiceServer() +} + +func RegisterACMEServiceServer(s grpc.ServiceRegistrar, srv ACMEServiceServer) { + // If the following call panics, it indicates UnimplementedACMEServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ACMEService_ServiceDesc, srv) +} + +func _ACMEService_CreateEAB_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateEABRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ACMEServiceServer).CreateEAB(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ACMEService_CreateEAB_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ACMEServiceServer).CreateEAB(ctx, req.(*CreateEABRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ACMEService_SetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetACMEConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ACMEServiceServer).SetConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ACMEService_SetConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ACMEServiceServer).SetConfig(ctx, req.(*SetACMEConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ACMEService_ListAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListACMEAccountsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ACMEServiceServer).ListAccounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ACMEService_ListAccounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ACMEServiceServer).ListAccounts(ctx, req.(*ListACMEAccountsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ACMEService_ListOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListACMEOrdersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ACMEServiceServer).ListOrders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ACMEService_ListOrders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ACMEServiceServer).ListOrders(ctx, req.(*ListACMEOrdersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ACMEService_ServiceDesc is the grpc.ServiceDesc for ACMEService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ACMEService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v1.ACMEService", + HandlerType: (*ACMEServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateEAB", + Handler: _ACMEService_CreateEAB_Handler, + }, + { + MethodName: "SetConfig", + Handler: _ACMEService_SetConfig_Handler, + }, + { + MethodName: "ListAccounts", + Handler: _ACMEService_ListAccounts_Handler, + }, + { + MethodName: "ListOrders", + Handler: _ACMEService_ListOrders_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "metacrypt/v1/acme.proto", +} diff --git a/gen/metacrypt/v1/auth.pb.go b/gen/metacrypt/v1/auth.pb.go new file mode 100644 index 0000000..3a1fddb --- /dev/null +++ b/gen/metacrypt/v1/auth.pb.go @@ -0,0 +1,386 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/auth.proto + +package metacryptv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LoginRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + TotpCode string `protobuf:"bytes,3,opt,name=totp_code,json=totpCode,proto3" json:"totp_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *LoginRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *LoginRequest) GetTotpCode() string { + if x != nil { + return x.TotpCode + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + ExpiresAt string `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *LoginResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *LoginResponse) GetExpiresAt() string { + if x != nil { + return x.ExpiresAt + } + return "" +} + +type LogoutRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogoutRequest) Reset() { + *x = LogoutRequest{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutRequest) ProtoMessage() {} + +func (x *LogoutRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. +func (*LogoutRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{2} +} + +type LogoutResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogoutResponse) Reset() { + *x = LogoutResponse{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutResponse) ProtoMessage() {} + +func (x *LogoutResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. +func (*LogoutResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{3} +} + +type TokenInfoRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TokenInfoRequest) Reset() { + *x = TokenInfoRequest{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TokenInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenInfoRequest) ProtoMessage() {} + +func (x *TokenInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenInfoRequest.ProtoReflect.Descriptor instead. +func (*TokenInfoRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{4} +} + +type TokenInfoResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` + IsAdmin bool `protobuf:"varint,3,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TokenInfoResponse) Reset() { + *x = TokenInfoResponse{} + mi := &file_metacrypt_v1_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TokenInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenInfoResponse) ProtoMessage() {} + +func (x *TokenInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_auth_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenInfoResponse.ProtoReflect.Descriptor instead. +func (*TokenInfoResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_auth_proto_rawDescGZIP(), []int{5} +} + +func (x *TokenInfoResponse) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *TokenInfoResponse) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *TokenInfoResponse) GetIsAdmin() bool { + if x != nil { + return x.IsAdmin + } + return false +} + +var File_metacrypt_v1_auth_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_auth_proto_rawDesc = "" + + "\n" + + "\x17metacrypt/v1/auth.proto\x12\fmetacrypt.v1\"c\n" + + "\fLoginRequest\x12\x1a\n" + + "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" + + "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1b\n" + + "\ttotp_code\x18\x03 \x01(\tR\btotpCode\"D\n" + + "\rLoginResponse\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12\x1d\n" + + "\n" + + "expires_at\x18\x02 \x01(\tR\texpiresAt\"\x0f\n" + + "\rLogoutRequest\"\x10\n" + + "\x0eLogoutResponse\"\x12\n" + + "\x10TokenInfoRequest\"`\n" + + "\x11TokenInfoResponse\x12\x1a\n" + + "\busername\x18\x01 \x01(\tR\busername\x12\x14\n" + + "\x05roles\x18\x02 \x03(\tR\x05roles\x12\x19\n" + + "\bis_admin\x18\x03 \x01(\bR\aisAdmin2\xe2\x01\n" + + "\vAuthService\x12@\n" + + "\x05Login\x12\x1a.metacrypt.v1.LoginRequest\x1a\x1b.metacrypt.v1.LoginResponse\x12C\n" + + "\x06Logout\x12\x1b.metacrypt.v1.LogoutRequest\x1a\x1c.metacrypt.v1.LogoutResponse\x12L\n" + + "\tTokenInfo\x12\x1e.metacrypt.v1.TokenInfoRequest\x1a\x1f.metacrypt.v1.TokenInfoResponseB>Z metacrypt.v1.LoginRequest + 2, // 1: metacrypt.v1.AuthService.Logout:input_type -> metacrypt.v1.LogoutRequest + 4, // 2: metacrypt.v1.AuthService.TokenInfo:input_type -> metacrypt.v1.TokenInfoRequest + 1, // 3: metacrypt.v1.AuthService.Login:output_type -> metacrypt.v1.LoginResponse + 3, // 4: metacrypt.v1.AuthService.Logout:output_type -> metacrypt.v1.LogoutResponse + 5, // 5: metacrypt.v1.AuthService.TokenInfo:output_type -> metacrypt.v1.TokenInfoResponse + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_metacrypt_v1_auth_proto_init() } +func file_metacrypt_v1_auth_proto_init() { + if File_metacrypt_v1_auth_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_auth_proto_rawDesc), len(file_metacrypt_v1_auth_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_metacrypt_v1_auth_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_auth_proto_depIdxs, + MessageInfos: file_metacrypt_v1_auth_proto_msgTypes, + }.Build() + File_metacrypt_v1_auth_proto = out.File + file_metacrypt_v1_auth_proto_goTypes = nil + file_metacrypt_v1_auth_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v1/auth_grpc.pb.go b/gen/metacrypt/v1/auth_grpc.pb.go new file mode 100644 index 0000000..f653178 --- /dev/null +++ b/gen/metacrypt/v1/auth_grpc.pb.go @@ -0,0 +1,197 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/auth.proto + +package metacryptv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AuthService_Login_FullMethodName = "/metacrypt.v1.AuthService/Login" + AuthService_Logout_FullMethodName = "/metacrypt.v1.AuthService/Logout" + AuthService_TokenInfo_FullMethodName = "/metacrypt.v1.AuthService/TokenInfo" +) + +// AuthServiceClient is the client API for AuthService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthServiceClient interface { + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) + Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) + TokenInfo(ctx context.Context, in *TokenInfoRequest, opts ...grpc.CallOption) (*TokenInfoResponse, error) +} + +type authServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { + return &authServiceClient{cc} +} + +func (c *authServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LoginResponse) + err := c.cc.Invoke(ctx, AuthService_Login_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LogoutResponse) + err := c.cc.Invoke(ctx, AuthService_Logout_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authServiceClient) TokenInfo(ctx context.Context, in *TokenInfoRequest, opts ...grpc.CallOption) (*TokenInfoResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TokenInfoResponse) + err := c.cc.Invoke(ctx, AuthService_TokenInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthServiceServer is the server API for AuthService service. +// All implementations must embed UnimplementedAuthServiceServer +// for forward compatibility. +type AuthServiceServer interface { + Login(context.Context, *LoginRequest) (*LoginResponse, error) + Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) + TokenInfo(context.Context, *TokenInfoRequest) (*TokenInfoResponse, error) + mustEmbedUnimplementedAuthServiceServer() +} + +// UnimplementedAuthServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAuthServiceServer struct{} + +func (UnimplementedAuthServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Logout not implemented") +} +func (UnimplementedAuthServiceServer) TokenInfo(context.Context, *TokenInfoRequest) (*TokenInfoResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TokenInfo not implemented") +} +func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} +func (UnimplementedAuthServiceServer) testEmbeddedByValue() {} + +// UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthServiceServer will +// result in compilation errors. +type UnsafeAuthServiceServer interface { + mustEmbedUnimplementedAuthServiceServer() +} + +func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { + // If the following call panics, it indicates UnimplementedAuthServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AuthService_ServiceDesc, srv) +} + +func _AuthService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_Login_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LogoutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).Logout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_Logout_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).Logout(ctx, req.(*LogoutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthService_TokenInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TokenInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).TokenInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_TokenInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).TokenInfo(ctx, req.(*TokenInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v1.AuthService", + HandlerType: (*AuthServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _AuthService_Login_Handler, + }, + { + MethodName: "Logout", + Handler: _AuthService_Logout_Handler, + }, + { + MethodName: "TokenInfo", + Handler: _AuthService_TokenInfo_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "metacrypt/v1/auth.proto", +} diff --git a/gen/metacrypt/v1/common.pb.go b/gen/metacrypt/v1/common.pb.go new file mode 100644 index 0000000..4af8d5a --- /dev/null +++ b/gen/metacrypt/v1/common.pb.go @@ -0,0 +1,59 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/common.proto + +package metacryptv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var File_metacrypt_v1_common_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_common_proto_rawDesc = "" + + "\n" + + "\x19metacrypt/v1/common.proto\x12\fmetacrypt.v1B>ZZ google.protobuf.Struct + 6, // 1: metacrypt.v1.ListMountsResponse.mounts:type_name -> metacrypt.v1.MountInfo + 9, // 2: metacrypt.v1.EngineRequest.data:type_name -> google.protobuf.Struct + 9, // 3: metacrypt.v1.EngineResponse.data:type_name -> google.protobuf.Struct + 0, // 4: metacrypt.v1.EngineService.Mount:input_type -> metacrypt.v1.MountRequest + 2, // 5: metacrypt.v1.EngineService.Unmount:input_type -> metacrypt.v1.UnmountRequest + 4, // 6: metacrypt.v1.EngineService.ListMounts:input_type -> metacrypt.v1.ListMountsRequest + 7, // 7: metacrypt.v1.EngineService.Request:input_type -> metacrypt.v1.EngineRequest + 1, // 8: metacrypt.v1.EngineService.Mount:output_type -> metacrypt.v1.MountResponse + 3, // 9: metacrypt.v1.EngineService.Unmount:output_type -> metacrypt.v1.UnmountResponse + 5, // 10: metacrypt.v1.EngineService.ListMounts:output_type -> metacrypt.v1.ListMountsResponse + 8, // 11: metacrypt.v1.EngineService.Request:output_type -> metacrypt.v1.EngineResponse + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_metacrypt_v1_engine_proto_init() } +func file_metacrypt_v1_engine_proto_init() { + if File_metacrypt_v1_engine_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_engine_proto_rawDesc), len(file_metacrypt_v1_engine_proto_rawDesc)), + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_metacrypt_v1_engine_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_engine_proto_depIdxs, + MessageInfos: file_metacrypt_v1_engine_proto_msgTypes, + }.Build() + File_metacrypt_v1_engine_proto = out.File + file_metacrypt_v1_engine_proto_goTypes = nil + file_metacrypt_v1_engine_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v1/engine_grpc.pb.go b/gen/metacrypt/v1/engine_grpc.pb.go new file mode 100644 index 0000000..8d5ec66 --- /dev/null +++ b/gen/metacrypt/v1/engine_grpc.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/engine.proto + +package metacryptv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + EngineService_Mount_FullMethodName = "/metacrypt.v1.EngineService/Mount" + EngineService_Unmount_FullMethodName = "/metacrypt.v1.EngineService/Unmount" + EngineService_ListMounts_FullMethodName = "/metacrypt.v1.EngineService/ListMounts" + EngineService_Request_FullMethodName = "/metacrypt.v1.EngineService/Request" +) + +// EngineServiceClient is the client API for EngineService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EngineServiceClient interface { + Mount(ctx context.Context, in *MountRequest, opts ...grpc.CallOption) (*MountResponse, error) + Unmount(ctx context.Context, in *UnmountRequest, opts ...grpc.CallOption) (*UnmountResponse, error) + ListMounts(ctx context.Context, in *ListMountsRequest, opts ...grpc.CallOption) (*ListMountsResponse, error) + Request(ctx context.Context, in *EngineRequest, opts ...grpc.CallOption) (*EngineResponse, error) +} + +type engineServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEngineServiceClient(cc grpc.ClientConnInterface) EngineServiceClient { + return &engineServiceClient{cc} +} + +func (c *engineServiceClient) Mount(ctx context.Context, in *MountRequest, opts ...grpc.CallOption) (*MountResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MountResponse) + err := c.cc.Invoke(ctx, EngineService_Mount_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) Unmount(ctx context.Context, in *UnmountRequest, opts ...grpc.CallOption) (*UnmountResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UnmountResponse) + err := c.cc.Invoke(ctx, EngineService_Unmount_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) ListMounts(ctx context.Context, in *ListMountsRequest, opts ...grpc.CallOption) (*ListMountsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListMountsResponse) + err := c.cc.Invoke(ctx, EngineService_ListMounts_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) Request(ctx context.Context, in *EngineRequest, opts ...grpc.CallOption) (*EngineResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EngineResponse) + err := c.cc.Invoke(ctx, EngineService_Request_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EngineServiceServer is the server API for EngineService service. +// All implementations must embed UnimplementedEngineServiceServer +// for forward compatibility. +type EngineServiceServer interface { + Mount(context.Context, *MountRequest) (*MountResponse, error) + Unmount(context.Context, *UnmountRequest) (*UnmountResponse, error) + ListMounts(context.Context, *ListMountsRequest) (*ListMountsResponse, error) + Request(context.Context, *EngineRequest) (*EngineResponse, error) + mustEmbedUnimplementedEngineServiceServer() +} + +// UnimplementedEngineServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEngineServiceServer struct{} + +func (UnimplementedEngineServiceServer) Mount(context.Context, *MountRequest) (*MountResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Mount not implemented") +} +func (UnimplementedEngineServiceServer) Unmount(context.Context, *UnmountRequest) (*UnmountResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Unmount not implemented") +} +func (UnimplementedEngineServiceServer) ListMounts(context.Context, *ListMountsRequest) (*ListMountsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListMounts not implemented") +} +func (UnimplementedEngineServiceServer) Request(context.Context, *EngineRequest) (*EngineResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Request not implemented") +} +func (UnimplementedEngineServiceServer) mustEmbedUnimplementedEngineServiceServer() {} +func (UnimplementedEngineServiceServer) testEmbeddedByValue() {} + +// UnsafeEngineServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EngineServiceServer will +// result in compilation errors. +type UnsafeEngineServiceServer interface { + mustEmbedUnimplementedEngineServiceServer() +} + +func RegisterEngineServiceServer(s grpc.ServiceRegistrar, srv EngineServiceServer) { + // If the following call panics, it indicates UnimplementedEngineServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EngineService_ServiceDesc, srv) +} + +func _EngineService_Mount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Mount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Mount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Mount(ctx, req.(*MountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_Unmount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnmountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Unmount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Unmount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Unmount(ctx, req.(*UnmountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_ListMounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListMountsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).ListMounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_ListMounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).ListMounts(ctx, req.(*ListMountsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_Request_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EngineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Request(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Request_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Request(ctx, req.(*EngineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EngineService_ServiceDesc is the grpc.ServiceDesc for EngineService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EngineService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v1.EngineService", + HandlerType: (*EngineServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Mount", + Handler: _EngineService_Mount_Handler, + }, + { + MethodName: "Unmount", + Handler: _EngineService_Unmount_Handler, + }, + { + MethodName: "ListMounts", + Handler: _EngineService_ListMounts_Handler, + }, + { + MethodName: "Request", + Handler: _EngineService_Request_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "metacrypt/v1/engine.proto", +} diff --git a/gen/metacrypt/v1/pki.pb.go b/gen/metacrypt/v1/pki.pb.go new file mode 100644 index 0000000..eaabfa6 --- /dev/null +++ b/gen/metacrypt/v1/pki.pb.go @@ -0,0 +1,386 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/pki.proto + +package metacryptv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetRootCertRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRootCertRequest) Reset() { + *x = GetRootCertRequest{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRootCertRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRootCertRequest) ProtoMessage() {} + +func (x *GetRootCertRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRootCertRequest.ProtoReflect.Descriptor instead. +func (*GetRootCertRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{0} +} + +func (x *GetRootCertRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +type GetRootCertResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + CertPem []byte `protobuf:"bytes,1,opt,name=cert_pem,json=certPem,proto3" json:"cert_pem,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRootCertResponse) Reset() { + *x = GetRootCertResponse{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRootCertResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRootCertResponse) ProtoMessage() {} + +func (x *GetRootCertResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRootCertResponse.ProtoReflect.Descriptor instead. +func (*GetRootCertResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{1} +} + +func (x *GetRootCertResponse) GetCertPem() []byte { + if x != nil { + return x.CertPem + } + return nil +} + +type GetChainRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Issuer string `protobuf:"bytes,2,opt,name=issuer,proto3" json:"issuer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetChainRequest) Reset() { + *x = GetChainRequest{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetChainRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetChainRequest) ProtoMessage() {} + +func (x *GetChainRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetChainRequest.ProtoReflect.Descriptor instead. +func (*GetChainRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{2} +} + +func (x *GetChainRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *GetChainRequest) GetIssuer() string { + if x != nil { + return x.Issuer + } + return "" +} + +type GetChainResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ChainPem []byte `protobuf:"bytes,1,opt,name=chain_pem,json=chainPem,proto3" json:"chain_pem,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetChainResponse) Reset() { + *x = GetChainResponse{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetChainResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetChainResponse) ProtoMessage() {} + +func (x *GetChainResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetChainResponse.ProtoReflect.Descriptor instead. +func (*GetChainResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{3} +} + +func (x *GetChainResponse) GetChainPem() []byte { + if x != nil { + return x.ChainPem + } + return nil +} + +type GetIssuerCertRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Issuer string `protobuf:"bytes,2,opt,name=issuer,proto3" json:"issuer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIssuerCertRequest) Reset() { + *x = GetIssuerCertRequest{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIssuerCertRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIssuerCertRequest) ProtoMessage() {} + +func (x *GetIssuerCertRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIssuerCertRequest.ProtoReflect.Descriptor instead. +func (*GetIssuerCertRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{4} +} + +func (x *GetIssuerCertRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *GetIssuerCertRequest) GetIssuer() string { + if x != nil { + return x.Issuer + } + return "" +} + +type GetIssuerCertResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + CertPem []byte `protobuf:"bytes,1,opt,name=cert_pem,json=certPem,proto3" json:"cert_pem,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIssuerCertResponse) Reset() { + *x = GetIssuerCertResponse{} + mi := &file_metacrypt_v1_pki_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIssuerCertResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIssuerCertResponse) ProtoMessage() {} + +func (x *GetIssuerCertResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_pki_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIssuerCertResponse.ProtoReflect.Descriptor instead. +func (*GetIssuerCertResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_pki_proto_rawDescGZIP(), []int{5} +} + +func (x *GetIssuerCertResponse) GetCertPem() []byte { + if x != nil { + return x.CertPem + } + return nil +} + +var File_metacrypt_v1_pki_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_pki_proto_rawDesc = "" + + "\n" + + "\x16metacrypt/v1/pki.proto\x12\fmetacrypt.v1\"*\n" + + "\x12GetRootCertRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\"0\n" + + "\x13GetRootCertResponse\x12\x19\n" + + "\bcert_pem\x18\x01 \x01(\fR\acertPem\"?\n" + + "\x0fGetChainRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x16\n" + + "\x06issuer\x18\x02 \x01(\tR\x06issuer\"/\n" + + "\x10GetChainResponse\x12\x1b\n" + + "\tchain_pem\x18\x01 \x01(\fR\bchainPem\"D\n" + + "\x14GetIssuerCertRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x16\n" + + "\x06issuer\x18\x02 \x01(\tR\x06issuer\"2\n" + + "\x15GetIssuerCertResponse\x12\x19\n" + + "\bcert_pem\x18\x01 \x01(\fR\acertPem2\x85\x02\n" + + "\n" + + "PKIService\x12R\n" + + "\vGetRootCert\x12 .metacrypt.v1.GetRootCertRequest\x1a!.metacrypt.v1.GetRootCertResponse\x12I\n" + + "\bGetChain\x12\x1d.metacrypt.v1.GetChainRequest\x1a\x1e.metacrypt.v1.GetChainResponse\x12X\n" + + "\rGetIssuerCert\x12\".metacrypt.v1.GetIssuerCertRequest\x1a#.metacrypt.v1.GetIssuerCertResponseB>Z metacrypt.v1.GetRootCertRequest + 2, // 1: metacrypt.v1.PKIService.GetChain:input_type -> metacrypt.v1.GetChainRequest + 4, // 2: metacrypt.v1.PKIService.GetIssuerCert:input_type -> metacrypt.v1.GetIssuerCertRequest + 1, // 3: metacrypt.v1.PKIService.GetRootCert:output_type -> metacrypt.v1.GetRootCertResponse + 3, // 4: metacrypt.v1.PKIService.GetChain:output_type -> metacrypt.v1.GetChainResponse + 5, // 5: metacrypt.v1.PKIService.GetIssuerCert:output_type -> metacrypt.v1.GetIssuerCertResponse + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_metacrypt_v1_pki_proto_init() } +func file_metacrypt_v1_pki_proto_init() { + if File_metacrypt_v1_pki_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_pki_proto_rawDesc), len(file_metacrypt_v1_pki_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_metacrypt_v1_pki_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_pki_proto_depIdxs, + MessageInfos: file_metacrypt_v1_pki_proto_msgTypes, + }.Build() + File_metacrypt_v1_pki_proto = out.File + file_metacrypt_v1_pki_proto_goTypes = nil + file_metacrypt_v1_pki_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v1/pki_grpc.pb.go b/gen/metacrypt/v1/pki_grpc.pb.go new file mode 100644 index 0000000..fdec00b --- /dev/null +++ b/gen/metacrypt/v1/pki_grpc.pb.go @@ -0,0 +1,203 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/pki.proto + +package metacryptv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + PKIService_GetRootCert_FullMethodName = "/metacrypt.v1.PKIService/GetRootCert" + PKIService_GetChain_FullMethodName = "/metacrypt.v1.PKIService/GetChain" + PKIService_GetIssuerCert_FullMethodName = "/metacrypt.v1.PKIService/GetIssuerCert" +) + +// PKIServiceClient is the client API for PKIService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// PKIService provides unauthenticated access to public CA certificates. +// These endpoints only require the service to be unsealed. +type PKIServiceClient interface { + GetRootCert(ctx context.Context, in *GetRootCertRequest, opts ...grpc.CallOption) (*GetRootCertResponse, error) + GetChain(ctx context.Context, in *GetChainRequest, opts ...grpc.CallOption) (*GetChainResponse, error) + GetIssuerCert(ctx context.Context, in *GetIssuerCertRequest, opts ...grpc.CallOption) (*GetIssuerCertResponse, error) +} + +type pKIServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPKIServiceClient(cc grpc.ClientConnInterface) PKIServiceClient { + return &pKIServiceClient{cc} +} + +func (c *pKIServiceClient) GetRootCert(ctx context.Context, in *GetRootCertRequest, opts ...grpc.CallOption) (*GetRootCertResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRootCertResponse) + err := c.cc.Invoke(ctx, PKIService_GetRootCert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pKIServiceClient) GetChain(ctx context.Context, in *GetChainRequest, opts ...grpc.CallOption) (*GetChainResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetChainResponse) + err := c.cc.Invoke(ctx, PKIService_GetChain_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pKIServiceClient) GetIssuerCert(ctx context.Context, in *GetIssuerCertRequest, opts ...grpc.CallOption) (*GetIssuerCertResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetIssuerCertResponse) + err := c.cc.Invoke(ctx, PKIService_GetIssuerCert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PKIServiceServer is the server API for PKIService service. +// All implementations must embed UnimplementedPKIServiceServer +// for forward compatibility. +// +// PKIService provides unauthenticated access to public CA certificates. +// These endpoints only require the service to be unsealed. +type PKIServiceServer interface { + GetRootCert(context.Context, *GetRootCertRequest) (*GetRootCertResponse, error) + GetChain(context.Context, *GetChainRequest) (*GetChainResponse, error) + GetIssuerCert(context.Context, *GetIssuerCertRequest) (*GetIssuerCertResponse, error) + mustEmbedUnimplementedPKIServiceServer() +} + +// UnimplementedPKIServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPKIServiceServer struct{} + +func (UnimplementedPKIServiceServer) GetRootCert(context.Context, *GetRootCertRequest) (*GetRootCertResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRootCert not implemented") +} +func (UnimplementedPKIServiceServer) GetChain(context.Context, *GetChainRequest) (*GetChainResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetChain not implemented") +} +func (UnimplementedPKIServiceServer) GetIssuerCert(context.Context, *GetIssuerCertRequest) (*GetIssuerCertResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetIssuerCert not implemented") +} +func (UnimplementedPKIServiceServer) mustEmbedUnimplementedPKIServiceServer() {} +func (UnimplementedPKIServiceServer) testEmbeddedByValue() {} + +// UnsafePKIServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PKIServiceServer will +// result in compilation errors. +type UnsafePKIServiceServer interface { + mustEmbedUnimplementedPKIServiceServer() +} + +func RegisterPKIServiceServer(s grpc.ServiceRegistrar, srv PKIServiceServer) { + // If the following call panics, it indicates UnimplementedPKIServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PKIService_ServiceDesc, srv) +} + +func _PKIService_GetRootCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRootCertRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PKIServiceServer).GetRootCert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PKIService_GetRootCert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PKIServiceServer).GetRootCert(ctx, req.(*GetRootCertRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PKIService_GetChain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetChainRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PKIServiceServer).GetChain(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PKIService_GetChain_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PKIServiceServer).GetChain(ctx, req.(*GetChainRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PKIService_GetIssuerCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIssuerCertRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PKIServiceServer).GetIssuerCert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PKIService_GetIssuerCert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PKIServiceServer).GetIssuerCert(ctx, req.(*GetIssuerCertRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PKIService_ServiceDesc is the grpc.ServiceDesc for PKIService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PKIService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v1.PKIService", + HandlerType: (*PKIServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetRootCert", + Handler: _PKIService_GetRootCert_Handler, + }, + { + MethodName: "GetChain", + Handler: _PKIService_GetChain_Handler, + }, + { + MethodName: "GetIssuerCert", + Handler: _PKIService_GetIssuerCert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "metacrypt/v1/pki.proto", +} diff --git a/gen/metacrypt/v1/policy.pb.go b/gen/metacrypt/v1/policy.pb.go new file mode 100644 index 0000000..429b319 --- /dev/null +++ b/gen/metacrypt/v1/policy.pb.go @@ -0,0 +1,456 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/policy.proto + +package metacryptv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PolicyRule struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Priority int32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty"` + Effect string `protobuf:"bytes,3,opt,name=effect,proto3" json:"effect,omitempty"` + Usernames []string `protobuf:"bytes,4,rep,name=usernames,proto3" json:"usernames,omitempty"` + Roles []string `protobuf:"bytes,5,rep,name=roles,proto3" json:"roles,omitempty"` + Resources []string `protobuf:"bytes,6,rep,name=resources,proto3" json:"resources,omitempty"` + Actions []string `protobuf:"bytes,7,rep,name=actions,proto3" json:"actions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyRule) Reset() { + *x = PolicyRule{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyRule) ProtoMessage() {} + +func (x *PolicyRule) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyRule.ProtoReflect.Descriptor instead. +func (*PolicyRule) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{0} +} + +func (x *PolicyRule) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *PolicyRule) GetPriority() int32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *PolicyRule) GetEffect() string { + if x != nil { + return x.Effect + } + return "" +} + +func (x *PolicyRule) GetUsernames() []string { + if x != nil { + return x.Usernames + } + return nil +} + +func (x *PolicyRule) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *PolicyRule) GetResources() []string { + if x != nil { + return x.Resources + } + return nil +} + +func (x *PolicyRule) GetActions() []string { + if x != nil { + return x.Actions + } + return nil +} + +type CreatePolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rule *PolicyRule `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePolicyRequest) Reset() { + *x = CreatePolicyRequest{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePolicyRequest) ProtoMessage() {} + +func (x *CreatePolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePolicyRequest.ProtoReflect.Descriptor instead. +func (*CreatePolicyRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{1} +} + +func (x *CreatePolicyRequest) GetRule() *PolicyRule { + if x != nil { + return x.Rule + } + return nil +} + +type ListPoliciesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPoliciesRequest) Reset() { + *x = ListPoliciesRequest{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPoliciesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPoliciesRequest) ProtoMessage() {} + +func (x *ListPoliciesRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPoliciesRequest.ProtoReflect.Descriptor instead. +func (*ListPoliciesRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{2} +} + +type ListPoliciesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rules []*PolicyRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPoliciesResponse) Reset() { + *x = ListPoliciesResponse{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPoliciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPoliciesResponse) ProtoMessage() {} + +func (x *ListPoliciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPoliciesResponse.ProtoReflect.Descriptor instead. +func (*ListPoliciesResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{3} +} + +func (x *ListPoliciesResponse) GetRules() []*PolicyRule { + if x != nil { + return x.Rules + } + return nil +} + +type GetPolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetPolicyRequest) Reset() { + *x = GetPolicyRequest{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPolicyRequest) ProtoMessage() {} + +func (x *GetPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPolicyRequest.ProtoReflect.Descriptor instead. +func (*GetPolicyRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{4} +} + +func (x *GetPolicyRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeletePolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePolicyRequest) Reset() { + *x = DeletePolicyRequest{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePolicyRequest) ProtoMessage() {} + +func (x *DeletePolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePolicyRequest.ProtoReflect.Descriptor instead. +func (*DeletePolicyRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{5} +} + +func (x *DeletePolicyRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeletePolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePolicyResponse) Reset() { + *x = DeletePolicyResponse{} + mi := &file_metacrypt_v1_policy_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePolicyResponse) ProtoMessage() {} + +func (x *DeletePolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_metacrypt_v1_policy_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePolicyResponse.ProtoReflect.Descriptor instead. +func (*DeletePolicyResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_policy_proto_rawDescGZIP(), []int{6} +} + +var File_metacrypt_v1_policy_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_policy_proto_rawDesc = "" + + "\n" + + "\x19metacrypt/v1/policy.proto\x12\fmetacrypt.v1\"\xbc\x01\n" + + "\n" + + "PolicyRule\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + + "\bpriority\x18\x02 \x01(\x05R\bpriority\x12\x16\n" + + "\x06effect\x18\x03 \x01(\tR\x06effect\x12\x1c\n" + + "\tusernames\x18\x04 \x03(\tR\tusernames\x12\x14\n" + + "\x05roles\x18\x05 \x03(\tR\x05roles\x12\x1c\n" + + "\tresources\x18\x06 \x03(\tR\tresources\x12\x18\n" + + "\aactions\x18\a \x03(\tR\aactions\"C\n" + + "\x13CreatePolicyRequest\x12,\n" + + "\x04rule\x18\x01 \x01(\v2\x18.metacrypt.v1.PolicyRuleR\x04rule\"\x15\n" + + "\x13ListPoliciesRequest\"F\n" + + "\x14ListPoliciesResponse\x12.\n" + + "\x05rules\x18\x01 \x03(\v2\x18.metacrypt.v1.PolicyRuleR\x05rules\"\"\n" + + "\x10GetPolicyRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"%\n" + + "\x13DeletePolicyRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x16\n" + + "\x14DeletePolicyResponse2\xd1\x02\n" + + "\rPolicyService\x12K\n" + + "\fCreatePolicy\x12!.metacrypt.v1.CreatePolicyRequest\x1a\x18.metacrypt.v1.PolicyRule\x12U\n" + + "\fListPolicies\x12!.metacrypt.v1.ListPoliciesRequest\x1a\".metacrypt.v1.ListPoliciesResponse\x12E\n" + + "\tGetPolicy\x12\x1e.metacrypt.v1.GetPolicyRequest\x1a\x18.metacrypt.v1.PolicyRule\x12U\n" + + "\fDeletePolicy\x12!.metacrypt.v1.DeletePolicyRequest\x1a\".metacrypt.v1.DeletePolicyResponseB>Z metacrypt.v1.PolicyRule + 0, // 1: metacrypt.v1.ListPoliciesResponse.rules:type_name -> metacrypt.v1.PolicyRule + 1, // 2: metacrypt.v1.PolicyService.CreatePolicy:input_type -> metacrypt.v1.CreatePolicyRequest + 2, // 3: metacrypt.v1.PolicyService.ListPolicies:input_type -> metacrypt.v1.ListPoliciesRequest + 4, // 4: metacrypt.v1.PolicyService.GetPolicy:input_type -> metacrypt.v1.GetPolicyRequest + 5, // 5: metacrypt.v1.PolicyService.DeletePolicy:input_type -> metacrypt.v1.DeletePolicyRequest + 0, // 6: metacrypt.v1.PolicyService.CreatePolicy:output_type -> metacrypt.v1.PolicyRule + 3, // 7: metacrypt.v1.PolicyService.ListPolicies:output_type -> metacrypt.v1.ListPoliciesResponse + 0, // 8: metacrypt.v1.PolicyService.GetPolicy:output_type -> metacrypt.v1.PolicyRule + 6, // 9: metacrypt.v1.PolicyService.DeletePolicy:output_type -> metacrypt.v1.DeletePolicyResponse + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_metacrypt_v1_policy_proto_init() } +func file_metacrypt_v1_policy_proto_init() { + if File_metacrypt_v1_policy_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_policy_proto_rawDesc), len(file_metacrypt_v1_policy_proto_rawDesc)), + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_metacrypt_v1_policy_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_policy_proto_depIdxs, + MessageInfos: file_metacrypt_v1_policy_proto_msgTypes, + }.Build() + File_metacrypt_v1_policy_proto = out.File + file_metacrypt_v1_policy_proto_goTypes = nil + file_metacrypt_v1_policy_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v1/policy_grpc.pb.go b/gen/metacrypt/v1/policy_grpc.pb.go new file mode 100644 index 0000000..24bd2a3 --- /dev/null +++ b/gen/metacrypt/v1/policy_grpc.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/policy.proto + +package metacryptv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + PolicyService_CreatePolicy_FullMethodName = "/metacrypt.v1.PolicyService/CreatePolicy" + PolicyService_ListPolicies_FullMethodName = "/metacrypt.v1.PolicyService/ListPolicies" + PolicyService_GetPolicy_FullMethodName = "/metacrypt.v1.PolicyService/GetPolicy" + PolicyService_DeletePolicy_FullMethodName = "/metacrypt.v1.PolicyService/DeletePolicy" +) + +// PolicyServiceClient is the client API for PolicyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PolicyServiceClient interface { + CreatePolicy(ctx context.Context, in *CreatePolicyRequest, opts ...grpc.CallOption) (*PolicyRule, error) + ListPolicies(ctx context.Context, in *ListPoliciesRequest, opts ...grpc.CallOption) (*ListPoliciesResponse, error) + GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*PolicyRule, error) + DeletePolicy(ctx context.Context, in *DeletePolicyRequest, opts ...grpc.CallOption) (*DeletePolicyResponse, error) +} + +type policyServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPolicyServiceClient(cc grpc.ClientConnInterface) PolicyServiceClient { + return &policyServiceClient{cc} +} + +func (c *policyServiceClient) CreatePolicy(ctx context.Context, in *CreatePolicyRequest, opts ...grpc.CallOption) (*PolicyRule, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicyRule) + err := c.cc.Invoke(ctx, PolicyService_CreatePolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyServiceClient) ListPolicies(ctx context.Context, in *ListPoliciesRequest, opts ...grpc.CallOption) (*ListPoliciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPoliciesResponse) + err := c.cc.Invoke(ctx, PolicyService_ListPolicies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyServiceClient) GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*PolicyRule, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicyRule) + err := c.cc.Invoke(ctx, PolicyService_GetPolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyServiceClient) DeletePolicy(ctx context.Context, in *DeletePolicyRequest, opts ...grpc.CallOption) (*DeletePolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeletePolicyResponse) + err := c.cc.Invoke(ctx, PolicyService_DeletePolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PolicyServiceServer is the server API for PolicyService service. +// All implementations must embed UnimplementedPolicyServiceServer +// for forward compatibility. +type PolicyServiceServer interface { + CreatePolicy(context.Context, *CreatePolicyRequest) (*PolicyRule, error) + ListPolicies(context.Context, *ListPoliciesRequest) (*ListPoliciesResponse, error) + GetPolicy(context.Context, *GetPolicyRequest) (*PolicyRule, error) + DeletePolicy(context.Context, *DeletePolicyRequest) (*DeletePolicyResponse, error) + mustEmbedUnimplementedPolicyServiceServer() +} + +// UnimplementedPolicyServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPolicyServiceServer struct{} + +func (UnimplementedPolicyServiceServer) CreatePolicy(context.Context, *CreatePolicyRequest) (*PolicyRule, error) { + return nil, status.Error(codes.Unimplemented, "method CreatePolicy not implemented") +} +func (UnimplementedPolicyServiceServer) ListPolicies(context.Context, *ListPoliciesRequest) (*ListPoliciesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPolicies not implemented") +} +func (UnimplementedPolicyServiceServer) GetPolicy(context.Context, *GetPolicyRequest) (*PolicyRule, error) { + return nil, status.Error(codes.Unimplemented, "method GetPolicy not implemented") +} +func (UnimplementedPolicyServiceServer) DeletePolicy(context.Context, *DeletePolicyRequest) (*DeletePolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeletePolicy not implemented") +} +func (UnimplementedPolicyServiceServer) mustEmbedUnimplementedPolicyServiceServer() {} +func (UnimplementedPolicyServiceServer) testEmbeddedByValue() {} + +// UnsafePolicyServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PolicyServiceServer will +// result in compilation errors. +type UnsafePolicyServiceServer interface { + mustEmbedUnimplementedPolicyServiceServer() +} + +func RegisterPolicyServiceServer(s grpc.ServiceRegistrar, srv PolicyServiceServer) { + // If the following call panics, it indicates UnimplementedPolicyServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PolicyService_ServiceDesc, srv) +} + +func _PolicyService_CreatePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServiceServer).CreatePolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PolicyService_CreatePolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServiceServer).CreatePolicy(ctx, req.(*CreatePolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolicyService_ListPolicies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPoliciesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServiceServer).ListPolicies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PolicyService_ListPolicies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServiceServer).ListPolicies(ctx, req.(*ListPoliciesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolicyService_GetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServiceServer).GetPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PolicyService_GetPolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServiceServer).GetPolicy(ctx, req.(*GetPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PolicyService_DeletePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServiceServer).DeletePolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PolicyService_DeletePolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServiceServer).DeletePolicy(ctx, req.(*DeletePolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PolicyService_ServiceDesc is the grpc.ServiceDesc for PolicyService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PolicyService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v1.PolicyService", + HandlerType: (*PolicyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreatePolicy", + Handler: _PolicyService_CreatePolicy_Handler, + }, + { + MethodName: "ListPolicies", + Handler: _PolicyService_ListPolicies_Handler, + }, + { + MethodName: "GetPolicy", + Handler: _PolicyService_GetPolicy_Handler, + }, + { + MethodName: "DeletePolicy", + Handler: _PolicyService_DeletePolicy_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "metacrypt/v1/policy.proto", +} diff --git a/gen/metacrypt/v1/system.pb.go b/gen/metacrypt/v1/system.pb.go index f25cfb3..fee8366 100644 --- a/gen/metacrypt/v1/system.pb.go +++ b/gen/metacrypt/v1/system.pb.go @@ -1,5 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: proto/metacrypt/v1/system.proto +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: metacrypt/v1/system.proto package metacryptv1 @@ -8,10 +11,13 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( + // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) @@ -23,14 +29,19 @@ type StatusRequest struct { func (x *StatusRequest) Reset() { *x = StatusRequest{} - mi := &file_system_proto_msgTypes[0] + mi := &file_metacrypt_v1_system_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StatusRequest) ProtoMessage() {} + +func (x *StatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusRequest) ProtoMessage() {} + func (x *StatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[0] + mi := &file_metacrypt_v1_system_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -41,6 +52,11 @@ func (x *StatusRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } +// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead. +func (*StatusRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{0} +} + type StatusResponse struct { state protoimpl.MessageState `protogen:"open.v1"` State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` @@ -50,14 +66,19 @@ type StatusResponse struct { func (x *StatusResponse) Reset() { *x = StatusResponse{} - mi := &file_system_proto_msgTypes[1] + mi := &file_metacrypt_v1_system_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StatusResponse) ProtoMessage() {} + +func (x *StatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResponse) ProtoMessage() {} + func (x *StatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[1] + mi := &file_metacrypt_v1_system_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -67,6 +88,12 @@ func (x *StatusResponse) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. +func (*StatusResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{1} +} + func (x *StatusResponse) GetState() string { if x != nil { return x.State @@ -83,14 +110,19 @@ type InitRequest struct { func (x *InitRequest) Reset() { *x = InitRequest{} - mi := &file_system_proto_msgTypes[2] + mi := &file_metacrypt_v1_system_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *InitRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*InitRequest) ProtoMessage() {} + +func (x *InitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitRequest) ProtoMessage() {} + func (x *InitRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[2] + mi := &file_metacrypt_v1_system_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -100,6 +132,12 @@ func (x *InitRequest) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use InitRequest.ProtoReflect.Descriptor instead. +func (*InitRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{2} +} + func (x *InitRequest) GetPassword() string { if x != nil { return x.Password @@ -116,14 +154,19 @@ type InitResponse struct { func (x *InitResponse) Reset() { *x = InitResponse{} - mi := &file_system_proto_msgTypes[3] + mi := &file_metacrypt_v1_system_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *InitResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*InitResponse) ProtoMessage() {} + +func (x *InitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitResponse) ProtoMessage() {} + func (x *InitResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[3] + mi := &file_metacrypt_v1_system_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -133,6 +176,12 @@ func (x *InitResponse) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use InitResponse.ProtoReflect.Descriptor instead. +func (*InitResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{3} +} + func (x *InitResponse) GetState() string { if x != nil { return x.State @@ -149,14 +198,19 @@ type UnsealRequest struct { func (x *UnsealRequest) Reset() { *x = UnsealRequest{} - mi := &file_system_proto_msgTypes[4] + mi := &file_metacrypt_v1_system_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UnsealRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UnsealRequest) ProtoMessage() {} + +func (x *UnsealRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsealRequest) ProtoMessage() {} + func (x *UnsealRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[4] + mi := &file_metacrypt_v1_system_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -166,6 +220,12 @@ func (x *UnsealRequest) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use UnsealRequest.ProtoReflect.Descriptor instead. +func (*UnsealRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{4} +} + func (x *UnsealRequest) GetPassword() string { if x != nil { return x.Password @@ -182,14 +242,19 @@ type UnsealResponse struct { func (x *UnsealResponse) Reset() { *x = UnsealResponse{} - mi := &file_system_proto_msgTypes[5] + mi := &file_metacrypt_v1_system_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UnsealResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UnsealResponse) ProtoMessage() {} + +func (x *UnsealResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsealResponse) ProtoMessage() {} + func (x *UnsealResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[5] + mi := &file_metacrypt_v1_system_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -199,6 +264,12 @@ func (x *UnsealResponse) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use UnsealResponse.ProtoReflect.Descriptor instead. +func (*UnsealResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{5} +} + func (x *UnsealResponse) GetState() string { if x != nil { return x.State @@ -214,14 +285,19 @@ type SealRequest struct { func (x *SealRequest) Reset() { *x = SealRequest{} - mi := &file_system_proto_msgTypes[6] + mi := &file_metacrypt_v1_system_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SealRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SealRequest) ProtoMessage() {} + +func (x *SealRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SealRequest) ProtoMessage() {} + func (x *SealRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[6] + mi := &file_metacrypt_v1_system_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -232,6 +308,11 @@ func (x *SealRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } +// Deprecated: Use SealRequest.ProtoReflect.Descriptor instead. +func (*SealRequest) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{6} +} + type SealResponse struct { state protoimpl.MessageState `protogen:"open.v1"` State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` @@ -241,14 +322,19 @@ type SealResponse struct { func (x *SealResponse) Reset() { *x = SealResponse{} - mi := &file_system_proto_msgTypes[7] + mi := &file_metacrypt_v1_system_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SealResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SealResponse) ProtoMessage() {} + +func (x *SealResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SealResponse) ProtoMessage() {} + func (x *SealResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[7] + mi := &file_metacrypt_v1_system_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -258,6 +344,12 @@ func (x *SealResponse) ProtoReflect() protoreflect.Message { } return mi.MessageOf(x) } + +// Deprecated: Use SealResponse.ProtoReflect.Descriptor instead. +func (*SealResponse) Descriptor() ([]byte, []int) { + return file_metacrypt_v1_system_proto_rawDescGZIP(), []int{7} +} + func (x *SealResponse) GetState() string { if x != nil { return x.State @@ -265,77 +357,55 @@ func (x *SealResponse) GetState() string { return "" } -// file descriptor compiled from proto/metacrypt/v1/system.proto -var file_system_proto_rawDesc = []byte{ - 0x0a, 0x1e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, 0x22, - 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x27, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x15, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x28, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x22, 0x25, 0x0a, 0x0c, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x2a, 0x0a, 0x0d, 0x55, 0x6e, 0x73, - 0x65, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x27, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x65, 0x61, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0d, - 0x0a, 0x0b, 0x53, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x25, 0x0a, - 0x0c, 0x53, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x32, 0xc7, 0x01, 0x0a, 0x0d, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1b, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, - 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x49, 0x6e, - 0x69, 0x74, 0x12, 0x19, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, - 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x69, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x55, 0x6e, 0x73, - 0x65, 0x61, 0x6c, 0x12, 0x1b, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x6e, 0x73, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, - 0x0a, 0x04, 0x53, 0x65, 0x61, 0x6c, 0x12, 0x19, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x5a, - 0x42, 0x67, 0x69, 0x74, 0x2e, 0x77, 0x6e, 0x74, 0x72, 0x6d, 0x75, 0x74, 0x65, 0x2e, 0x64, 0x65, - 0x76, 0x2f, 0x6b, 0x79, 0x6c, 0x65, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x76, - 0x31, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +var File_metacrypt_v1_system_proto protoreflect.FileDescriptor + +const file_metacrypt_v1_system_proto_rawDesc = "" + + "\n" + + "\x19metacrypt/v1/system.proto\x12\fmetacrypt.v1\"\x0f\n" + + "\rStatusRequest\"&\n" + + "\x0eStatusResponse\x12\x14\n" + + "\x05state\x18\x01 \x01(\tR\x05state\")\n" + + "\vInitRequest\x12\x1a\n" + + "\bpassword\x18\x01 \x01(\tR\bpassword\"$\n" + + "\fInitResponse\x12\x14\n" + + "\x05state\x18\x01 \x01(\tR\x05state\"+\n" + + "\rUnsealRequest\x12\x1a\n" + + "\bpassword\x18\x01 \x01(\tR\bpassword\"&\n" + + "\x0eUnsealResponse\x12\x14\n" + + "\x05state\x18\x01 \x01(\tR\x05state\"\r\n" + + "\vSealRequest\"$\n" + + "\fSealResponse\x12\x14\n" + + "\x05state\x18\x01 \x01(\tR\x05state2\x97\x02\n" + + "\rSystemService\x12C\n" + + "\x06Status\x12\x1b.metacrypt.v1.StatusRequest\x1a\x1c.metacrypt.v1.StatusResponse\x12=\n" + + "\x04Init\x12\x19.metacrypt.v1.InitRequest\x1a\x1a.metacrypt.v1.InitResponse\x12C\n" + + "\x06Unseal\x12\x1b.metacrypt.v1.UnsealRequest\x1a\x1c.metacrypt.v1.UnsealResponse\x12=\n" + + "\x04Seal\x12\x19.metacrypt.v1.SealRequest\x1a\x1a.metacrypt.v1.SealResponseB>Z metacrypt.v1.StatusRequest 2, // 1: metacrypt.v1.SystemService.Init:input_type -> metacrypt.v1.InitRequest 4, // 2: metacrypt.v1.SystemService.Unseal:input_type -> metacrypt.v1.UnsealRequest @@ -351,31 +421,26 @@ var file_system_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_system_proto_init() } - -func file_system_proto_init() { - if File_system_proto != nil { +func init() { file_metacrypt_v1_system_proto_init() } +func file_metacrypt_v1_system_proto_init() { + if File_metacrypt_v1_system_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_system_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metacrypt_v1_system_proto_rawDesc), len(file_metacrypt_v1_system_proto_rawDesc)), NumEnums: 0, NumMessages: 8, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_system_proto_goTypes, - DependencyIndexes: file_system_proto_depIdxs, - MessageInfos: file_system_proto_msgTypes, + GoTypes: file_metacrypt_v1_system_proto_goTypes, + DependencyIndexes: file_metacrypt_v1_system_proto_depIdxs, + MessageInfos: file_metacrypt_v1_system_proto_msgTypes, }.Build() - File_system_proto = out.File - file_system_proto_rawDescData = nil - file_system_proto_goTypes = nil - file_system_proto_depIdxs = nil + File_metacrypt_v1_system_proto = out.File + file_metacrypt_v1_system_proto_goTypes = nil + file_metacrypt_v1_system_proto_depIdxs = nil } - -// File_system_proto is the protoreflect.FileDescriptor for proto/metacrypt/v1/system.proto. -var File_system_proto protoreflect.FileDescriptor diff --git a/gen/metacrypt/v1/system_grpc.pb.go b/gen/metacrypt/v1/system_grpc.pb.go index a2e6b74..a9c8f3a 100644 --- a/gen/metacrypt/v1/system_grpc.pb.go +++ b/gen/metacrypt/v1/system_grpc.pb.go @@ -1,17 +1,33 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// source: proto/metacrypt/v1/system.proto +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: metacrypt/v1/system.proto package metacryptv1 import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) -// SystemServiceClient is the client API for SystemService. +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + SystemService_Status_FullMethodName = "/metacrypt.v1.SystemService/Status" + SystemService_Init_FullMethodName = "/metacrypt.v1.SystemService/Init" + SystemService_Unseal_FullMethodName = "/metacrypt.v1.SystemService/Unseal" + SystemService_Seal_FullMethodName = "/metacrypt.v1.SystemService/Seal" +) + +// SystemServiceClient is the client API for SystemService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type SystemServiceClient interface { Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*InitResponse, error) @@ -67,7 +83,9 @@ func (c *systemServiceClient) Seal(ctx context.Context, in *SealRequest, opts .. return out, nil } -// SystemServiceServer is the server API for SystemService. +// SystemServiceServer is the server API for SystemService service. +// All implementations must embed UnimplementedSystemServiceServer +// for forward compatibility. type SystemServiceServer interface { Status(context.Context, *StatusRequest) (*StatusResponse, error) Init(context.Context, *InitRequest) (*InitResponse, error) @@ -76,36 +94,43 @@ type SystemServiceServer interface { mustEmbedUnimplementedSystemServiceServer() } -// UnimplementedSystemServiceServer must be embedded to have forward-compatible implementations. +// UnimplementedSystemServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. type UnimplementedSystemServiceServer struct{} func (UnimplementedSystemServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") + return nil, status.Error(codes.Unimplemented, "method Status not implemented") } func (UnimplementedSystemServiceServer) Init(context.Context, *InitRequest) (*InitResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Init not implemented") + return nil, status.Error(codes.Unimplemented, "method Init not implemented") } func (UnimplementedSystemServiceServer) Unseal(context.Context, *UnsealRequest) (*UnsealResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Unseal not implemented") + return nil, status.Error(codes.Unimplemented, "method Unseal not implemented") } func (UnimplementedSystemServiceServer) Seal(context.Context, *SealRequest) (*SealResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Seal not implemented") + return nil, status.Error(codes.Unimplemented, "method Seal not implemented") } func (UnimplementedSystemServiceServer) mustEmbedUnimplementedSystemServiceServer() {} +func (UnimplementedSystemServiceServer) testEmbeddedByValue() {} -// UnsafeSystemServiceServer may be embedded to opt out of forward compatibility. +// UnsafeSystemServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SystemServiceServer will +// result in compilation errors. type UnsafeSystemServiceServer interface { mustEmbedUnimplementedSystemServiceServer() } -const ( - SystemService_Status_FullMethodName = "/metacrypt.v1.SystemService/Status" - SystemService_Init_FullMethodName = "/metacrypt.v1.SystemService/Init" - SystemService_Unseal_FullMethodName = "/metacrypt.v1.SystemService/Unseal" - SystemService_Seal_FullMethodName = "/metacrypt.v1.SystemService/Seal" -) - func RegisterSystemServiceServer(s grpc.ServiceRegistrar, srv SystemServiceServer) { + // If the following call panics, it indicates UnimplementedSystemServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&SystemService_ServiceDesc, srv) } @@ -182,6 +207,8 @@ func _SystemService_Seal_Handler(srv interface{}, ctx context.Context, dec func( } // SystemService_ServiceDesc is the grpc.ServiceDesc for SystemService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) var SystemService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "metacrypt.v1.SystemService", HandlerType: (*SystemServiceServer)(nil), @@ -204,5 +231,5 @@ var SystemService_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "proto/metacrypt/v1/system.proto", + Metadata: "metacrypt/v1/system.proto", } diff --git a/go.mod b/go.mod index 0a4afac..cbe9a3e 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/spf13/viper v1.21.0 golang.org/x/crypto v0.49.0 golang.org/x/term v0.41.0 - google.golang.org/grpc v1.71.1 - google.golang.org/protobuf v1.36.5 + google.golang.org/grpc v1.79.2 + google.golang.org/protobuf v1.36.11 modernc.org/sqlite v1.46.1 ) @@ -37,10 +37,10 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/net v0.40.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 510834e..98b69d8 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,7 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -65,6 +66,8 @@ golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -76,6 +79,12 @@ golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/config.go b/internal/config/config.go index 8f423db..ba63b4a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,7 @@ import ( // Config is the top-level configuration for Metacrypt. type Config struct { Server ServerConfig `toml:"server"` + Web WebConfig `toml:"web"` Database DatabaseConfig `toml:"database"` MCIAS MCIASConfig `toml:"mcias"` Seal SealConfig `toml:"seal"` @@ -26,6 +27,20 @@ type ServerConfig struct { ExternalURL string `toml:"external_url"` // public base URL for ACME directory, e.g. "https://metacrypt.example.com" } +// WebConfig holds settings for the standalone web UI server (metacrypt-web). +type WebConfig struct { + // ListenAddr is the address the web server listens on (default: 127.0.0.1:8080). + ListenAddr string `toml:"listen_addr"` + // VaultGRPC is the gRPC address of the vault server (e.g. "127.0.0.1:9443"). + VaultGRPC string `toml:"vault_grpc"` + // VaultCACert is the path to the CA certificate used to verify the vault's TLS cert. + VaultCACert string `toml:"vault_ca_cert"` + // TLSCert and TLSKey are optional. If empty, the web server uses plain HTTP + // (suitable for deployment behind a TLS-terminating reverse proxy). + TLSCert string `toml:"tls_cert"` + TLSKey string `toml:"tls_key"` +} + // DatabaseConfig holds SQLite database settings. type DatabaseConfig struct { Path string `toml:"path"` @@ -98,5 +113,10 @@ func (c *Config) Validate() error { c.Log.Level = "info" } + // Apply defaults for web server. + if c.Web.ListenAddr == "" { + c.Web.ListenAddr = "127.0.0.1:8080" + } + return nil } diff --git a/internal/grpcserver/acme.go b/internal/grpcserver/acme.go new file mode 100644 index 0000000..cc467e8 --- /dev/null +++ b/internal/grpcserver/acme.go @@ -0,0 +1,130 @@ +package grpcserver + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + internacme "git.wntrmute.dev/kyle/metacrypt/internal/acme" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" +) + +type acmeServer struct { + pb.UnimplementedACMEServiceServer + s *GRPCServer +} + +func (as *acmeServer) CreateEAB(ctx context.Context, req *pb.CreateEABRequest) (*pb.CreateEABResponse, error) { + ti := tokenInfoFromContext(ctx) + h, err := as.getOrCreateHandler(req.Mount) + if err != nil { + return nil, status.Error(codes.NotFound, "mount not found") + } + cred, err := h.CreateEAB(ctx, ti.Username) + if err != nil { + as.s.logger.Error("grpc: acme create EAB", "error", err) + return nil, status.Error(codes.Internal, "failed to create EAB credentials") + } + return &pb.CreateEABResponse{Kid: cred.KID, HmacKey: cred.HMACKey}, nil +} + +func (as *acmeServer) SetConfig(ctx context.Context, req *pb.SetACMEConfigRequest) (*pb.SetACMEConfigResponse, error) { + if req.DefaultIssuer == "" { + return nil, status.Error(codes.InvalidArgument, "default_issuer is required") + } + // Verify mount exists. + if _, err := as.getOrCreateHandler(req.Mount); err != nil { + return nil, status.Error(codes.NotFound, "mount not found") + } + cfg := &internacme.ACMEConfig{DefaultIssuer: req.DefaultIssuer} + data, _ := json.Marshal(cfg) + barrierPath := "acme/" + req.Mount + "/config.json" + if err := as.s.sealMgr.Barrier().Put(ctx, barrierPath, data); err != nil { + as.s.logger.Error("grpc: acme set config", "error", err) + return nil, status.Error(codes.Internal, "failed to save config") + } + return &pb.SetACMEConfigResponse{Ok: true}, nil +} + +func (as *acmeServer) ListAccounts(ctx context.Context, req *pb.ListACMEAccountsRequest) (*pb.ListACMEAccountsResponse, error) { + h, err := as.getOrCreateHandler(req.Mount) + if err != nil { + return nil, status.Error(codes.NotFound, "mount not found") + } + accounts, err := h.ListAccounts(ctx) + if err != nil { + as.s.logger.Error("grpc: acme list accounts", "error", err) + return nil, status.Error(codes.Internal, "internal error") + } + pbAccounts := make([]*pb.ACMEAccount, 0, len(accounts)) + for _, a := range accounts { + contacts := make([]string, len(a.Contact)) + copy(contacts, a.Contact) + pbAccounts = append(pbAccounts, &pb.ACMEAccount{ + Id: a.ID, + Status: a.Status, + Contact: contacts, + MciasUsername: a.MCIASUsername, + CreatedAt: a.CreatedAt.String(), + }) + } + return &pb.ListACMEAccountsResponse{Accounts: pbAccounts}, nil +} + +func (as *acmeServer) ListOrders(ctx context.Context, req *pb.ListACMEOrdersRequest) (*pb.ListACMEOrdersResponse, error) { + h, err := as.getOrCreateHandler(req.Mount) + if err != nil { + return nil, status.Error(codes.NotFound, "mount not found") + } + orders, err := h.ListOrders(ctx) + if err != nil { + as.s.logger.Error("grpc: acme list orders", "error", err) + return nil, status.Error(codes.Internal, "internal error") + } + pbOrders := make([]*pb.ACMEOrder, 0, len(orders)) + for _, o := range orders { + identifiers := make([]string, 0, len(o.Identifiers)) + for _, id := range o.Identifiers { + identifiers = append(identifiers, id.Type+":"+id.Value) + } + pbOrders = append(pbOrders, &pb.ACMEOrder{ + Id: o.ID, + AccountId: o.AccountID, + Status: o.Status, + Identifiers: identifiers, + CreatedAt: o.CreatedAt.String(), + ExpiresAt: o.ExpiresAt.String(), + }) + } + return &pb.ListACMEOrdersResponse{Orders: pbOrders}, nil +} + +func (as *acmeServer) getOrCreateHandler(mountName string) (*internacme.Handler, error) { + as.s.mu.Lock() + defer as.s.mu.Unlock() + + // Verify mount is a CA engine. + mount, err := as.s.engines.GetMount(mountName) + if err != nil { + return nil, err + } + if mount.Type != engine.EngineTypeCA { + return nil, engine.ErrMountNotFound + } + + // Check handler cache on GRPCServer. + if h, ok := as.s.acmeHandlers[mountName]; ok { + return h, nil + } + + baseURL := as.s.cfg.Server.ExternalURL + if baseURL == "" { + baseURL = "https://" + as.s.cfg.Server.ListenAddr + } + h := internacme.NewHandler(mountName, as.s.sealMgr.Barrier(), as.s.engines, baseURL, as.s.logger) + as.s.acmeHandlers[mountName] = h + return h, nil +} diff --git a/internal/grpcserver/auth.go b/internal/grpcserver/auth.go new file mode 100644 index 0000000..5f2e687 --- /dev/null +++ b/internal/grpcserver/auth.go @@ -0,0 +1,56 @@ +package grpcserver + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + mcias "git.wntrmute.dev/kyle/mcias/clients/go" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" +) + +type authServer struct { + pb.UnimplementedAuthServiceServer + s *GRPCServer +} + +func (as *authServer) Login(_ context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) { + token, expiresAt, err := as.s.auth.Login(req.Username, req.Password, req.TotpCode) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "invalid credentials") + } + return &pb.LoginResponse{Token: token, ExpiresAt: expiresAt}, nil +} + +func (as *authServer) Logout(ctx context.Context, _ *pb.LogoutRequest) (*pb.LogoutResponse, error) { + token := extractToken(ctx) + client, err := mcias.New(as.s.cfg.MCIAS.ServerURL, mcias.Options{ + CACertPath: as.s.cfg.MCIAS.CACert, + Token: token, + }) + if err == nil { + as.s.auth.Logout(client) + } + return &pb.LogoutResponse{}, nil +} + +func (as *authServer) TokenInfo(ctx context.Context, _ *pb.TokenInfoRequest) (*pb.TokenInfoResponse, error) { + ti := tokenInfoFromContext(ctx) + if ti == nil { + // Shouldn't happen — authInterceptor runs first — but guard anyway. + token := extractToken(ctx) + var err error + ti, err = as.s.auth.ValidateToken(token) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "invalid token") + } + } + return &pb.TokenInfoResponse{ + Username: ti.Username, + Roles: ti.Roles, + IsAdmin: ti.IsAdmin, + }, nil +} + diff --git a/internal/grpcserver/engine.go b/internal/grpcserver/engine.go new file mode 100644 index 0000000..20f5153 --- /dev/null +++ b/internal/grpcserver/engine.go @@ -0,0 +1,112 @@ +package grpcserver + +import ( + "context" + "errors" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/structpb" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" +) + +type engineServer struct { + pb.UnimplementedEngineServiceServer + s *GRPCServer +} + +func (es *engineServer) Mount(ctx context.Context, req *pb.MountRequest) (*pb.MountResponse, error) { + if req.Name == "" || req.Type == "" { + return nil, status.Error(codes.InvalidArgument, "name and type are required") + } + + var config map[string]interface{} + if req.Config != nil { + config = req.Config.AsMap() + } + + if err := es.s.engines.Mount(ctx, req.Name, engine.EngineType(req.Type), config); err != nil { + es.s.logger.Error("grpc: mount engine", "name", req.Name, "type", req.Type, "error", err) + switch { + case errors.Is(err, engine.ErrMountExists): + return nil, status.Error(codes.AlreadyExists, err.Error()) + case errors.Is(err, engine.ErrUnknownType): + return nil, status.Error(codes.InvalidArgument, err.Error()) + default: + return nil, status.Error(codes.Internal, err.Error()) + } + } + return &pb.MountResponse{}, nil +} + +func (es *engineServer) Unmount(ctx context.Context, req *pb.UnmountRequest) (*pb.UnmountResponse, error) { + if req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "name is required") + } + if err := es.s.engines.Unmount(ctx, req.Name); err != nil { + if errors.Is(err, engine.ErrMountNotFound) { + return nil, status.Error(codes.NotFound, err.Error()) + } + return nil, status.Error(codes.Internal, err.Error()) + } + return &pb.UnmountResponse{}, nil +} + +func (es *engineServer) ListMounts(_ context.Context, _ *pb.ListMountsRequest) (*pb.ListMountsResponse, error) { + mounts := es.s.engines.ListMounts() + pbMounts := make([]*pb.MountInfo, 0, len(mounts)) + for _, m := range mounts { + pbMounts = append(pbMounts, &pb.MountInfo{ + Name: m.Name, + Type: string(m.Type), + MountPath: m.MountPath, + }) + } + return &pb.ListMountsResponse{Mounts: pbMounts}, nil +} + +func (es *engineServer) Request(ctx context.Context, req *pb.EngineRequest) (*pb.EngineResponse, error) { + if req.Mount == "" || req.Operation == "" { + return nil, status.Error(codes.InvalidArgument, "mount and operation are required") + } + + ti := tokenInfoFromContext(ctx) + engReq := &engine.Request{ + Operation: req.Operation, + Path: req.Path, + Data: nil, + } + if req.Data != nil { + engReq.Data = req.Data.AsMap() + } + if ti != nil { + engReq.CallerInfo = &engine.CallerInfo{ + Username: ti.Username, + Roles: ti.Roles, + IsAdmin: ti.IsAdmin, + } + } + + resp, err := es.s.engines.HandleRequest(ctx, req.Mount, engReq) + if err != nil { + st := codes.Internal + switch { + case errors.Is(err, engine.ErrMountNotFound): + st = codes.NotFound + case strings.Contains(err.Error(), "forbidden"): + st = codes.PermissionDenied + case strings.Contains(err.Error(), "not found"): + st = codes.NotFound + } + return nil, status.Error(st, err.Error()) + } + + pbData, err := structpb.NewStruct(resp.Data) + if err != nil { + return nil, status.Error(codes.Internal, "failed to encode response") + } + return &pb.EngineResponse{Data: pbData}, nil +} diff --git a/internal/grpcserver/interceptors.go b/internal/grpcserver/interceptors.go new file mode 100644 index 0000000..503ee67 --- /dev/null +++ b/internal/grpcserver/interceptors.go @@ -0,0 +1,107 @@ +package grpcserver + +import ( + "context" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "git.wntrmute.dev/kyle/metacrypt/internal/auth" + "git.wntrmute.dev/kyle/metacrypt/internal/seal" +) + +type contextKey int + +const tokenInfoKey contextKey = iota + +func tokenInfoFromContext(ctx context.Context) *auth.TokenInfo { + v, _ := ctx.Value(tokenInfoKey).(*auth.TokenInfo) + return v +} + +// authInterceptor validates the Bearer token from gRPC metadata and injects +// *auth.TokenInfo into the context. The set of method full names that require +// auth is passed in; all others pass through without validation. +func authInterceptor(authenticator *auth.Authenticator, methods map[string]bool) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if !methods[info.FullMethod] { + return handler(ctx, req) + } + + token := extractToken(ctx) + if token == "" { + return nil, status.Error(codes.Unauthenticated, "missing authorization token") + } + + tokenInfo, err := authenticator.ValidateToken(token) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "invalid token") + } + + ctx = context.WithValue(ctx, tokenInfoKey, tokenInfo) + return handler(ctx, req) + } +} + +// adminInterceptor requires IsAdmin on the token info for the listed methods. +// Must run after authInterceptor. +func adminInterceptor(methods map[string]bool) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if !methods[info.FullMethod] { + return handler(ctx, req) + } + ti := tokenInfoFromContext(ctx) + if ti == nil || !ti.IsAdmin { + return nil, status.Error(codes.PermissionDenied, "admin required") + } + return handler(ctx, req) + } +} + +// sealInterceptor rejects calls with FailedPrecondition when the vault is +// sealed, for the listed methods. +func sealInterceptor(sealMgr *seal.Manager, methods map[string]bool) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if !methods[info.FullMethod] { + return handler(ctx, req) + } + if sealMgr.State() != seal.StateUnsealed { + return nil, status.Error(codes.FailedPrecondition, "vault is sealed") + } + return handler(ctx, req) + } +} + +// chainInterceptors reduces a slice of interceptors into a single one. +func chainInterceptors(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + chain := handler + for i := len(interceptors) - 1; i >= 0; i-- { + next := chain + ic := interceptors[i] + chain = func(ctx context.Context, req interface{}) (interface{}, error) { + return ic(ctx, req, info, next) + } + } + return chain(ctx, req) + } +} + +func extractToken(ctx context.Context) string { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "" + } + vals := md.Get("authorization") + if len(vals) == 0 { + return "" + } + v := vals[0] + if strings.HasPrefix(v, "Bearer ") { + return strings.TrimPrefix(v, "Bearer ") + } + return v +} diff --git a/internal/grpcserver/pki.go b/internal/grpcserver/pki.go new file mode 100644 index 0000000..5e5c77b --- /dev/null +++ b/internal/grpcserver/pki.go @@ -0,0 +1,81 @@ +package grpcserver + +import ( + "context" + "errors" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" + "git.wntrmute.dev/kyle/metacrypt/internal/engine/ca" +) + +type pkiServer struct { + pb.UnimplementedPKIServiceServer + s *GRPCServer +} + +func (ps *pkiServer) GetRootCert(_ context.Context, req *pb.GetRootCertRequest) (*pb.GetRootCertResponse, error) { + caEng, err := ps.getCAEngine(req.Mount) + if err != nil { + return nil, err + } + certPEM, err := caEng.GetRootCertPEM() + if err != nil { + return nil, status.Error(codes.Unavailable, "sealed") + } + return &pb.GetRootCertResponse{CertPem: certPEM}, nil +} + +func (ps *pkiServer) GetChain(_ context.Context, req *pb.GetChainRequest) (*pb.GetChainResponse, error) { + if req.Issuer == "" { + return nil, status.Error(codes.InvalidArgument, "issuer is required") + } + caEng, err := ps.getCAEngine(req.Mount) + if err != nil { + return nil, err + } + chainPEM, err := caEng.GetChainPEM(req.Issuer) + if err != nil { + if errors.Is(err, ca.ErrIssuerNotFound) { + return nil, status.Error(codes.NotFound, "issuer not found") + } + return nil, status.Error(codes.Unavailable, "sealed") + } + return &pb.GetChainResponse{ChainPem: chainPEM}, nil +} + +func (ps *pkiServer) GetIssuerCert(_ context.Context, req *pb.GetIssuerCertRequest) (*pb.GetIssuerCertResponse, error) { + if req.Issuer == "" { + return nil, status.Error(codes.InvalidArgument, "issuer is required") + } + caEng, err := ps.getCAEngine(req.Mount) + if err != nil { + return nil, err + } + certPEM, err := caEng.GetIssuerCertPEM(req.Issuer) + if err != nil { + if errors.Is(err, ca.ErrIssuerNotFound) { + return nil, status.Error(codes.NotFound, "issuer not found") + } + return nil, status.Error(codes.Unavailable, "sealed") + } + return &pb.GetIssuerCertResponse{CertPem: certPEM}, nil +} + +func (ps *pkiServer) getCAEngine(mountName string) (*ca.CAEngine, error) { + mount, err := ps.s.engines.GetMount(mountName) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + if mount.Type != engine.EngineTypeCA { + return nil, status.Error(codes.NotFound, "mount is not a CA engine") + } + caEng, ok := mount.Engine.(*ca.CAEngine) + if !ok { + return nil, status.Error(codes.NotFound, "mount is not a CA engine") + } + return caEng, nil +} diff --git a/internal/grpcserver/policy.go b/internal/grpcserver/policy.go new file mode 100644 index 0000000..09581fc --- /dev/null +++ b/internal/grpcserver/policy.go @@ -0,0 +1,86 @@ +package grpcserver + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + "git.wntrmute.dev/kyle/metacrypt/internal/policy" +) + +type policyServer struct { + pb.UnimplementedPolicyServiceServer + s *GRPCServer +} + +func (ps *policyServer) CreatePolicy(ctx context.Context, req *pb.CreatePolicyRequest) (*pb.PolicyRule, error) { + if req.Rule == nil || req.Rule.Id == "" { + return nil, status.Error(codes.InvalidArgument, "rule.id is required") + } + rule := pbToRule(req.Rule) + if err := ps.s.policy.CreateRule(ctx, rule); err != nil { + ps.s.logger.Error("grpc: create policy", "error", err) + return nil, status.Error(codes.Internal, "internal error") + } + return ruleToPB(rule), nil +} + +func (ps *policyServer) ListPolicies(ctx context.Context, _ *pb.ListPoliciesRequest) (*pb.ListPoliciesResponse, error) { + rules, err := ps.s.policy.ListRules(ctx) + if err != nil { + ps.s.logger.Error("grpc: list policies", "error", err) + return nil, status.Error(codes.Internal, "internal error") + } + pbRules := make([]*pb.PolicyRule, 0, len(rules)) + for i := range rules { + pbRules = append(pbRules, ruleToPB(&rules[i])) + } + return &pb.ListPoliciesResponse{Rules: pbRules}, nil +} + +func (ps *policyServer) GetPolicy(ctx context.Context, req *pb.GetPolicyRequest) (*pb.PolicyRule, error) { + if req.Id == "" { + return nil, status.Error(codes.InvalidArgument, "id is required") + } + rule, err := ps.s.policy.GetRule(ctx, req.Id) + if err != nil { + return nil, status.Error(codes.NotFound, "not found") + } + return ruleToPB(rule), nil +} + +func (ps *policyServer) DeletePolicy(ctx context.Context, req *pb.DeletePolicyRequest) (*pb.DeletePolicyResponse, error) { + if req.Id == "" { + return nil, status.Error(codes.InvalidArgument, "id is required") + } + if err := ps.s.policy.DeleteRule(ctx, req.Id); err != nil { + return nil, status.Error(codes.NotFound, "not found") + } + return &pb.DeletePolicyResponse{}, nil +} + +func pbToRule(r *pb.PolicyRule) *policy.Rule { + return &policy.Rule{ + ID: r.Id, + Priority: int(r.Priority), + Effect: policy.Effect(r.Effect), + Usernames: r.Usernames, + Roles: r.Roles, + Resources: r.Resources, + Actions: r.Actions, + } +} + +func ruleToPB(r *policy.Rule) *pb.PolicyRule { + return &pb.PolicyRule{ + Id: r.ID, + Priority: int32(r.Priority), + Effect: string(r.Effect), + Usernames: r.Usernames, + Roles: r.Roles, + Resources: r.Resources, + Actions: r.Actions, + } +} diff --git a/internal/grpcserver/server.go b/internal/grpcserver/server.go new file mode 100644 index 0000000..e3206c8 --- /dev/null +++ b/internal/grpcserver/server.go @@ -0,0 +1,162 @@ +// Package grpcserver implements the gRPC server for Metacrypt. +package grpcserver + +import ( + "crypto/tls" + "fmt" + "log/slog" + "net" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + internacme "git.wntrmute.dev/kyle/metacrypt/internal/acme" + "git.wntrmute.dev/kyle/metacrypt/internal/auth" + "git.wntrmute.dev/kyle/metacrypt/internal/config" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" + "git.wntrmute.dev/kyle/metacrypt/internal/policy" + "git.wntrmute.dev/kyle/metacrypt/internal/seal" +) + +// GRPCServer wraps the gRPC server and all service implementations. +type GRPCServer struct { + cfg *config.Config + sealMgr *seal.Manager + auth *auth.Authenticator + policy *policy.Engine + engines *engine.Registry + logger *slog.Logger + + srv *grpc.Server + mu sync.Mutex + acmeHandlers map[string]*internacme.Handler +} + +// New creates a new GRPCServer. +func New(cfg *config.Config, sealMgr *seal.Manager, authenticator *auth.Authenticator, + policyEngine *policy.Engine, engineRegistry *engine.Registry, logger *slog.Logger) *GRPCServer { + return &GRPCServer{ + cfg: cfg, + sealMgr: sealMgr, + auth: authenticator, + policy: policyEngine, + engines: engineRegistry, + logger: logger, + acmeHandlers: make(map[string]*internacme.Handler), + } +} + +// Start starts the gRPC server on cfg.Server.GRPCAddr. +func (s *GRPCServer) Start() error { + if s.cfg.Server.GRPCAddr == "" { + s.logger.Info("grpc_addr not configured, gRPC server disabled") + return nil + } + + tlsCert, err := tls.LoadX509KeyPair(s.cfg.Server.TLSCert, s.cfg.Server.TLSKey) + if err != nil { + return fmt.Errorf("grpc: load TLS cert: %w", err) + } + tlsCfg := &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + MinVersion: tls.VersionTLS12, + } + creds := credentials.NewTLS(tlsCfg) + + interceptor := chainInterceptors( + sealInterceptor(s.sealMgr, sealRequiredMethods()), + authInterceptor(s.auth, authRequiredMethods()), + adminInterceptor(adminRequiredMethods()), + ) + + s.srv = grpc.NewServer( + grpc.Creds(creds), + grpc.UnaryInterceptor(interceptor), + ) + + pb.RegisterSystemServiceServer(s.srv, &systemServer{s: s}) + pb.RegisterAuthServiceServer(s.srv, &authServer{s: s}) + pb.RegisterEngineServiceServer(s.srv, &engineServer{s: s}) + pb.RegisterPKIServiceServer(s.srv, &pkiServer{s: s}) + pb.RegisterPolicyServiceServer(s.srv, &policyServer{s: s}) + pb.RegisterACMEServiceServer(s.srv, &acmeServer{s: s}) + + lis, err := net.Listen("tcp", s.cfg.Server.GRPCAddr) + if err != nil { + return fmt.Errorf("grpc: listen %s: %w", s.cfg.Server.GRPCAddr, err) + } + + s.logger.Info("starting gRPC server", "addr", s.cfg.Server.GRPCAddr) + if err := s.srv.Serve(lis); err != nil { + return fmt.Errorf("grpc: serve: %w", err) + } + return nil +} + +// Shutdown gracefully stops the gRPC server. +func (s *GRPCServer) Shutdown() { + if s.srv != nil { + s.srv.GracefulStop() + } +} + +// sealRequiredMethods returns the set of RPC full names that require the vault +// to be unsealed. +func sealRequiredMethods() map[string]bool { + return map[string]bool{ + "/metacrypt.v1.AuthService/Login": true, + "/metacrypt.v1.AuthService/Logout": true, + "/metacrypt.v1.AuthService/TokenInfo": true, + "/metacrypt.v1.EngineService/Mount": true, + "/metacrypt.v1.EngineService/Unmount": true, + "/metacrypt.v1.EngineService/ListMounts": true, + "/metacrypt.v1.EngineService/Request": true, + "/metacrypt.v1.PKIService/GetRootCert": true, + "/metacrypt.v1.PKIService/GetChain": true, + "/metacrypt.v1.PKIService/GetIssuerCert": true, + "/metacrypt.v1.PolicyService/CreatePolicy": true, + "/metacrypt.v1.PolicyService/ListPolicies": true, + "/metacrypt.v1.PolicyService/GetPolicy": true, + "/metacrypt.v1.PolicyService/DeletePolicy": true, + "/metacrypt.v1.ACMEService/CreateEAB": true, + "/metacrypt.v1.ACMEService/SetConfig": true, + "/metacrypt.v1.ACMEService/ListAccounts": true, + "/metacrypt.v1.ACMEService/ListOrders": true, + } +} + +// authRequiredMethods returns the set of RPC full names that require a valid token. +func authRequiredMethods() map[string]bool { + return map[string]bool{ + "/metacrypt.v1.AuthService/Logout": true, + "/metacrypt.v1.AuthService/TokenInfo": true, + "/metacrypt.v1.EngineService/Mount": true, + "/metacrypt.v1.EngineService/Unmount": true, + "/metacrypt.v1.EngineService/ListMounts": true, + "/metacrypt.v1.EngineService/Request": true, + "/metacrypt.v1.PolicyService/CreatePolicy": true, + "/metacrypt.v1.PolicyService/ListPolicies": true, + "/metacrypt.v1.PolicyService/GetPolicy": true, + "/metacrypt.v1.PolicyService/DeletePolicy": true, + "/metacrypt.v1.ACMEService/CreateEAB": true, + "/metacrypt.v1.ACMEService/SetConfig": true, + "/metacrypt.v1.ACMEService/ListAccounts": true, + "/metacrypt.v1.ACMEService/ListOrders": true, + } +} + +// adminRequiredMethods returns the set of RPC full names that require admin. +func adminRequiredMethods() map[string]bool { + return map[string]bool{ + "/metacrypt.v1.SystemService/Seal": true, + "/metacrypt.v1.EngineService/Mount": true, + "/metacrypt.v1.EngineService/Unmount": true, + "/metacrypt.v1.PolicyService/CreatePolicy": true, + "/metacrypt.v1.PolicyService/DeletePolicy": true, + "/metacrypt.v1.ACMEService/SetConfig": true, + "/metacrypt.v1.ACMEService/ListAccounts": true, + "/metacrypt.v1.ACMEService/ListOrders": true, + } +} diff --git a/internal/grpcserver/system.go b/internal/grpcserver/system.go new file mode 100644 index 0000000..13c5c1b --- /dev/null +++ b/internal/grpcserver/system.go @@ -0,0 +1,80 @@ +package grpcserver + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" + "git.wntrmute.dev/kyle/metacrypt/internal/crypto" + "git.wntrmute.dev/kyle/metacrypt/internal/seal" +) + +type systemServer struct { + pb.UnimplementedSystemServiceServer + s *GRPCServer +} + +func (ss *systemServer) Status(_ context.Context, _ *pb.StatusRequest) (*pb.StatusResponse, error) { + return &pb.StatusResponse{State: ss.s.sealMgr.State().String()}, nil +} + +func (ss *systemServer) Init(ctx context.Context, req *pb.InitRequest) (*pb.InitResponse, error) { + if req.Password == "" { + return nil, status.Error(codes.InvalidArgument, "password is required") + } + + params := crypto.Argon2Params{ + Time: ss.s.cfg.Seal.Argon2Time, + Memory: ss.s.cfg.Seal.Argon2Memory, + Threads: ss.s.cfg.Seal.Argon2Threads, + } + if err := ss.s.sealMgr.Initialize(ctx, []byte(req.Password), params); err != nil { + switch err { + case seal.ErrAlreadyInitialized: + return nil, status.Error(codes.AlreadyExists, "already initialized") + default: + ss.s.logger.Error("grpc: init failed", "error", err) + return nil, status.Error(codes.Internal, "initialization failed") + } + } + return &pb.InitResponse{State: ss.s.sealMgr.State().String()}, nil +} + +func (ss *systemServer) Unseal(ctx context.Context, req *pb.UnsealRequest) (*pb.UnsealResponse, error) { + if err := ss.s.sealMgr.Unseal([]byte(req.Password)); err != nil { + switch err { + case seal.ErrNotInitialized: + return nil, status.Error(codes.FailedPrecondition, "not initialized") + case seal.ErrInvalidPassword: + return nil, status.Error(codes.Unauthenticated, "invalid password") + case seal.ErrRateLimited: + return nil, status.Error(codes.ResourceExhausted, "too many attempts, try again later") + case seal.ErrNotSealed: + return nil, status.Error(codes.FailedPrecondition, "already unsealed") + default: + ss.s.logger.Error("grpc: unseal failed", "error", err) + return nil, status.Error(codes.Internal, "unseal failed") + } + } + + if err := ss.s.engines.UnsealAll(ctx); err != nil { + ss.s.logger.Error("grpc: engine unseal failed", "error", err) + return nil, status.Error(codes.Internal, "engine unseal failed") + } + + return &pb.UnsealResponse{State: ss.s.sealMgr.State().String()}, nil +} + +func (ss *systemServer) Seal(_ context.Context, _ *pb.SealRequest) (*pb.SealResponse, error) { + if err := ss.s.engines.SealAll(); err != nil { + ss.s.logger.Error("grpc: seal engines failed", "error", err) + } + if err := ss.s.sealMgr.Seal(); err != nil { + ss.s.logger.Error("grpc: seal failed", "error", err) + return nil, status.Error(codes.Internal, "seal failed") + } + ss.s.auth.ClearCache() + return &pb.SealResponse{State: ss.s.sealMgr.State().String()}, nil +} diff --git a/internal/server/routes.go b/internal/server/routes.go index 288f064..c62d0b6 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -1,24 +1,16 @@ package server import ( - "context" - "crypto/x509" "encoding/json" - "encoding/pem" "errors" - "fmt" - "html/template" "io" "net/http" - "path/filepath" "strings" - "time" "github.com/go-chi/chi/v5" mcias "git.wntrmute.dev/kyle/mcias/clients/go" - "git.wntrmute.dev/kyle/metacrypt/internal/auth" "git.wntrmute.dev/kyle/metacrypt/internal/crypto" "git.wntrmute.dev/kyle/metacrypt/internal/engine" "git.wntrmute.dev/kyle/metacrypt/internal/engine/ca" @@ -27,25 +19,7 @@ import ( ) func (s *Server) registerRoutes(r chi.Router) { - // Static files. - r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) - - // Web UI routes. - r.Get("/", s.handleWebRoot) - r.HandleFunc("/init", s.handleWebInit) - r.HandleFunc("/unseal", s.handleWebUnseal) - r.HandleFunc("/login", s.handleWebLogin) - r.Get("/dashboard", s.requireAuthWeb(s.handleWebDashboard)) - r.Post("/dashboard/mount-ca", s.requireAuthWeb(s.handleWebDashboardMountCA)) - - r.Route("/pki", func(r chi.Router) { - r.Get("/", s.requireAuthWeb(s.handleWebPKI)) - r.Post("/import-root", s.requireAuthWeb(s.handleWebImportRoot)) - r.Post("/create-issuer", s.requireAuthWeb(s.handleWebCreateIssuer)) - r.Get("/{issuer}", s.requireAuthWeb(s.handleWebPKIIssuer)) - }) - - // API routes. + // REST API routes — web UI served by metacrypt-web. r.Get("/v1/status", s.handleStatus) r.Post("/v1/init", s.handleInit) r.Post("/v1/unseal", s.handleUnseal) @@ -67,7 +41,8 @@ func (s *Server) registerRoutes(r chi.Router) { r.HandleFunc("/v1/policy/rules", s.requireAuth(s.handlePolicyRules)) r.HandleFunc("/v1/policy/rule", s.requireAuth(s.handlePolicyRule)) - s.registerACMERoutes(r)} + s.registerACMERoutes(r) +} // --- API Handlers --- @@ -478,474 +453,6 @@ func (s *Server) getCAEngine(mountName string) (*ca.CAEngine, error) { return caEng, nil } -// findCAMount returns the name of the first CA engine mount. -func (s *Server) findCAMount() (string, error) { - for _, m := range s.engines.ListMounts() { - if m.Type == engine.EngineTypeCA { - return m.Name, nil - } - } - return "", errors.New("no CA engine mounted") -} - -// --- Web Handlers --- - -func (s *Server) handleWebRoot(w http.ResponseWriter, r *http.Request) { - state := s.seal.State() - switch state { - case seal.StateUninitialized: - http.Redirect(w, r, "/init", http.StatusFound) - case seal.StateSealed: - http.Redirect(w, r, "/unseal", http.StatusFound) - case seal.StateInitializing: - http.Redirect(w, r, "/init", http.StatusFound) - case seal.StateUnsealed: - http.Redirect(w, r, "/dashboard", http.StatusFound) - } -} - -func (s *Server) handleWebInit(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - if s.seal.State() != seal.StateUninitialized { - http.Redirect(w, r, "/", http.StatusFound) - return - } - s.renderTemplate(w, "init.html", nil) - case http.MethodPost: - r.ParseForm() - password := r.FormValue("password") - if password == "" { - s.renderTemplate(w, "init.html", map[string]interface{}{"Error": "Password is required"}) - return - } - params := crypto.Argon2Params{ - Time: s.cfg.Seal.Argon2Time, - Memory: s.cfg.Seal.Argon2Memory, - Threads: s.cfg.Seal.Argon2Threads, - } - if err := s.seal.Initialize(r.Context(), []byte(password), params); err != nil { - s.renderTemplate(w, "init.html", map[string]interface{}{"Error": err.Error()}) - return - } - http.Redirect(w, r, "/dashboard", http.StatusFound) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } -} - -func (s *Server) handleWebUnseal(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - state := s.seal.State() - if state == seal.StateUninitialized { - http.Redirect(w, r, "/init", http.StatusFound) - return - } - if state == seal.StateUnsealed { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - s.renderTemplate(w, "unseal.html", nil) - case http.MethodPost: - r.ParseForm() - password := r.FormValue("password") - if err := s.seal.Unseal([]byte(password)); err != nil { - msg := "Invalid password" - if err == seal.ErrRateLimited { - msg = "Too many attempts. Please wait 60 seconds." - } - s.renderTemplate(w, "unseal.html", map[string]interface{}{"Error": msg}) - return - } - if err := s.engines.UnsealAll(r.Context()); err != nil { - s.logger.Error("engine unseal failed", "error", err) - s.renderTemplate(w, "unseal.html", map[string]interface{}{"Error": "Engine reload failed: " + err.Error()}) - return - } - http.Redirect(w, r, "/dashboard", http.StatusFound) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } -} - -func (s *Server) handleWebLogin(w http.ResponseWriter, r *http.Request) { - if s.seal.State() != seal.StateUnsealed { - http.Redirect(w, r, "/", http.StatusFound) - return - } - switch r.Method { - case http.MethodGet: - s.renderTemplate(w, "login.html", nil) - case http.MethodPost: - r.ParseForm() - username := r.FormValue("username") - password := r.FormValue("password") - totpCode := r.FormValue("totp_code") - token, _, err := s.auth.Login(username, password, totpCode) - if err != nil { - s.renderTemplate(w, "login.html", map[string]interface{}{"Error": "Invalid credentials"}) - return - } - http.SetCookie(w, &http.Cookie{ - Name: "metacrypt_token", - Value: token, - Path: "/", - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteStrictMode, - }) - http.Redirect(w, r, "/dashboard", http.StatusFound) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } -} - -func (s *Server) handleWebDashboard(w http.ResponseWriter, r *http.Request) { - info := TokenInfoFromContext(r.Context()) - mounts := s.engines.ListMounts() - s.renderTemplate(w, "dashboard.html", map[string]interface{}{ - "Username": info.Username, - "IsAdmin": info.IsAdmin, - "Roles": info.Roles, - "Mounts": mounts, - "State": s.seal.State().String(), - "Version": s.version, - }) -} - -func (s *Server) handleWebDashboardMountCA(w http.ResponseWriter, r *http.Request) { - info := TokenInfoFromContext(r.Context()) - if !info.IsAdmin { - http.Error(w, "forbidden", http.StatusForbidden) - return - } - - if err := r.ParseMultipartForm(1 << 20); err != nil { - r.ParseForm() - } - - mountName := r.FormValue("name") - if mountName == "" { - s.renderDashboardWithError(w, r, info, "Mount name is required") - return - } - - config := map[string]interface{}{} - if org := r.FormValue("organization"); org != "" { - config["organization"] = org - } - - // Optional root CA import. - var certPEM, keyPEM string - if f, _, err := r.FormFile("cert_file"); err == nil { - defer f.Close() - data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) - certPEM = string(data) - } - if f, _, err := r.FormFile("key_file"); err == nil { - defer f.Close() - data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) - keyPEM = string(data) - } - if certPEM != "" && keyPEM != "" { - config["root_cert_pem"] = certPEM - config["root_key_pem"] = keyPEM - } - - if err := s.engines.Mount(r.Context(), mountName, engine.EngineTypeCA, config); err != nil { - s.renderDashboardWithError(w, r, info, err.Error()) - return - } - - http.Redirect(w, r, "/pki", http.StatusFound) -} - -func (s *Server) renderDashboardWithError(w http.ResponseWriter, _ *http.Request, info *auth.TokenInfo, errMsg string) { - mounts := s.engines.ListMounts() - s.renderTemplate(w, "dashboard.html", map[string]interface{}{ - "Username": info.Username, - "IsAdmin": info.IsAdmin, - "Roles": info.Roles, - "Mounts": mounts, - "State": s.seal.State().String(), - "MountError": errMsg, - }) -} - -func (s *Server) handleWebPKI(w http.ResponseWriter, r *http.Request) { - info := TokenInfoFromContext(r.Context()) - - mountName, err := s.findCAMount() - if err != nil { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - caEng, err := s.getCAEngine(mountName) - if err != nil { - http.Redirect(w, r, "/dashboard", http.StatusFound) - return - } - - data := map[string]interface{}{ - "Username": info.Username, - "IsAdmin": info.IsAdmin, - "MountName": mountName, - } - - // Get root cert info. - rootPEM, err := caEng.GetRootCertPEM() - if err == nil && rootPEM != nil { - if cert, err := parsePEMCert(rootPEM); err == nil { - data["RootCN"] = cert.Subject.CommonName - data["RootOrg"] = strings.Join(cert.Subject.Organization, ", ") - data["RootNotBefore"] = cert.NotBefore.Format(time.RFC3339) - data["RootNotAfter"] = cert.NotAfter.Format(time.RFC3339) - data["RootExpired"] = time.Now().After(cert.NotAfter) - data["HasRoot"] = true - } - } - - // Get issuers. - callerInfo := &engine.CallerInfo{ - Username: info.Username, - Roles: info.Roles, - IsAdmin: info.IsAdmin, - } - resp, err := s.engines.HandleRequest(r.Context(), mountName, &engine.Request{ - Operation: "list-issuers", - CallerInfo: callerInfo, - }) - if err == nil { - data["Issuers"] = resp.Data["issuers"] - } - - s.renderTemplate(w, "pki.html", data) -} - -func (s *Server) handleWebImportRoot(w http.ResponseWriter, r *http.Request) { - info := TokenInfoFromContext(r.Context()) - if !info.IsAdmin { - http.Error(w, "forbidden", http.StatusForbidden) - return - } - - mountName, err := s.findCAMount() - if err != nil { - http.Error(w, "no CA engine mounted", http.StatusNotFound) - return - } - - if err := r.ParseMultipartForm(1 << 20); err != nil { - r.ParseForm() - } - - certPEM := r.FormValue("cert_pem") - keyPEM := r.FormValue("key_pem") - - // Also support file uploads. - if certPEM == "" { - if f, _, err := r.FormFile("cert_file"); err == nil { - defer f.Close() - data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) - certPEM = string(data) - } - } - if keyPEM == "" { - if f, _, err := r.FormFile("key_file"); err == nil { - defer f.Close() - data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) - keyPEM = string(data) - } - } - - if certPEM == "" || keyPEM == "" { - s.renderPKIWithError(w, r, mountName, info, "Certificate and private key are required") - return - } - - callerInfo := &engine.CallerInfo{ - Username: info.Username, - Roles: info.Roles, - IsAdmin: info.IsAdmin, - } - _, err = s.engines.HandleRequest(r.Context(), mountName, &engine.Request{ - Operation: "import-root", - CallerInfo: callerInfo, - Data: map[string]interface{}{ - "cert_pem": certPEM, - "key_pem": keyPEM, - }, - }) - if err != nil { - s.renderPKIWithError(w, r, mountName, info, err.Error()) - return - } - - http.Redirect(w, r, "/pki", http.StatusFound) -} - -func (s *Server) handleWebCreateIssuer(w http.ResponseWriter, r *http.Request) { - info := TokenInfoFromContext(r.Context()) - if !info.IsAdmin { - http.Error(w, "forbidden", http.StatusForbidden) - return - } - - mountName, err := s.findCAMount() - if err != nil { - http.Error(w, "no CA engine mounted", http.StatusNotFound) - return - } - - r.ParseForm() - - name := r.FormValue("name") - if name == "" { - s.renderPKIWithError(w, r, mountName, info, "Issuer name is required") - return - } - - data := map[string]interface{}{ - "name": name, - } - if v := r.FormValue("expiry"); v != "" { - data["expiry"] = v - } - if v := r.FormValue("max_ttl"); v != "" { - data["max_ttl"] = v - } - if v := r.FormValue("key_algorithm"); v != "" { - data["key_algorithm"] = v - } - if v := r.FormValue("key_size"); v != "" { - // Parse as float64 to match JSON number convention used by the engine. - var size float64 - if _, err := fmt.Sscanf(v, "%f", &size); err == nil { - data["key_size"] = size - } - } - - callerInfo := &engine.CallerInfo{ - Username: info.Username, - Roles: info.Roles, - IsAdmin: info.IsAdmin, - } - _, err = s.engines.HandleRequest(r.Context(), mountName, &engine.Request{ - Operation: "create-issuer", - CallerInfo: callerInfo, - Data: data, - }) - if err != nil { - s.renderPKIWithError(w, r, mountName, info, err.Error()) - return - } - - http.Redirect(w, r, "/pki", http.StatusFound) -} - -func (s *Server) handleWebPKIIssuer(w http.ResponseWriter, r *http.Request) { - mountName, err := s.findCAMount() - if err != nil { - http.Error(w, "no CA engine mounted", http.StatusNotFound) - return - } - - issuerName := chi.URLParam(r, "issuer") - caEng, err := s.getCAEngine(mountName) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - certPEM, err := caEng.GetIssuerCertPEM(issuerName) - if err != nil { - http.Error(w, "issuer not found", http.StatusNotFound) - return - } - - w.Header().Set("Content-Type", "application/x-pem-file") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.pem", issuerName)) - w.Write(certPEM) -} - -func (s *Server) renderPKIWithError(w http.ResponseWriter, r *http.Request, mountName string, info *auth.TokenInfo, errMsg string) { - data := map[string]interface{}{ - "Username": info.Username, - "IsAdmin": info.IsAdmin, - "MountName": mountName, - "Error": errMsg, - } - - // Try to load existing root info. - mount, merr := s.engines.GetMount(mountName) - if merr == nil && mount.Type == engine.EngineTypeCA { - if caEng, ok := mount.Engine.(*ca.CAEngine); ok { - rootPEM, err := caEng.GetRootCertPEM() - if err == nil && rootPEM != nil { - if cert, err := parsePEMCert(rootPEM); err == nil { - data["RootCN"] = cert.Subject.CommonName - data["RootOrg"] = strings.Join(cert.Subject.Organization, ", ") - data["RootNotBefore"] = cert.NotBefore.Format(time.RFC3339) - data["RootNotAfter"] = cert.NotAfter.Format(time.RFC3339) - data["RootExpired"] = time.Now().After(cert.NotAfter) - data["HasRoot"] = true - } - } - } - } - - s.renderTemplate(w, "pki.html", data) -} - -func parsePEMCert(pemData []byte) (*x509.Certificate, error) { - block, _ := pem.Decode(pemData) - if block == nil { - return nil, errors.New("no PEM block found") - } - return x509.ParseCertificate(block.Bytes) -} - -// requireAuthWeb redirects to login for web pages instead of returning 401. -func (s *Server) requireAuthWeb(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if s.seal.State() != seal.StateUnsealed { - http.Redirect(w, r, "/", http.StatusFound) - return - } - token := extractToken(r) - if token == "" { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - info, err := s.auth.ValidateToken(token) - if err != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - ctx := r.Context() - ctx = context.WithValue(ctx, tokenInfoKey, info) - next(w, r.WithContext(ctx)) - } -} - -func (s *Server) renderTemplate(w http.ResponseWriter, name string, data interface{}) { - tmpl, err := template.ParseFiles( - filepath.Join("web", "templates", "layout.html"), - filepath.Join("web", "templates", name), - ) - if err != nil { - s.logger.Error("parse template", "name", name, "error", err) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - if err := tmpl.ExecuteTemplate(w, "layout", data); err != nil { - s.logger.Error("execute template", "name", name, "error", err) - } -} - func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 702d531..91e3bac 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -151,18 +151,15 @@ func TestStatusMethodNotAllowed(t *testing.T) { } } -func TestRootRedirect(t *testing.T) { +func TestRootNotFound(t *testing.T) { _, _, mux := setupTestServer(t) + // The vault server no longer serves a web UI at /; that is handled by metacrypt-web. req := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) - if w.Code != http.StatusFound { - t.Errorf("root redirect: got %d, want %d", w.Code, http.StatusFound) - } - loc := w.Header().Get("Location") - if loc != "/init" { - t.Errorf("redirect location: got %q, want /init", loc) + if w.Code != http.StatusNotFound { + t.Errorf("root: got %d, want %d", w.Code, http.StatusNotFound) } } diff --git a/internal/webserver/client.go b/internal/webserver/client.go new file mode 100644 index 0000000..c8b059a --- /dev/null +++ b/internal/webserver/client.go @@ -0,0 +1,199 @@ +package webserver + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v1" +) + +// VaultClient wraps the gRPC stubs for communicating with the vault. +type VaultClient struct { + conn *grpc.ClientConn + system pb.SystemServiceClient + auth pb.AuthServiceClient + engine pb.EngineServiceClient + pki pb.PKIServiceClient +} + +// NewVaultClient dials the vault gRPC server and returns a client. +func NewVaultClient(addr, caCertPath string) (*VaultClient, error) { + tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12} + if caCertPath != "" { + pemData, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("webserver: read CA cert: %w", err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("webserver: parse CA cert") + } + tlsCfg.RootCAs = pool + } + + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) + if err != nil { + return nil, fmt.Errorf("webserver: dial vault: %w", err) + } + + return &VaultClient{ + conn: conn, + system: pb.NewSystemServiceClient(conn), + auth: pb.NewAuthServiceClient(conn), + engine: pb.NewEngineServiceClient(conn), + pki: pb.NewPKIServiceClient(conn), + }, nil +} + +// Close closes the underlying connection. +func (c *VaultClient) Close() error { + return c.conn.Close() +} + +// withToken returns a context with the Bearer token in outgoing metadata. +func withToken(ctx context.Context, token string) context.Context { + return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token) +} + +// Status returns the current vault state string (e.g. "unsealed"). +func (c *VaultClient) Status(ctx context.Context) (string, error) { + resp, err := c.system.Status(ctx, &pb.StatusRequest{}) + if err != nil { + return "", err + } + return resp.State, nil +} + +// Init initializes the vault with the given password. +func (c *VaultClient) Init(ctx context.Context, password string) error { + _, err := c.system.Init(ctx, &pb.InitRequest{Password: password}) + return err +} + +// Unseal unseals the vault with the given password. +func (c *VaultClient) Unseal(ctx context.Context, password string) error { + _, err := c.system.Unseal(ctx, &pb.UnsealRequest{Password: password}) + return err +} + +// TokenInfo holds validated token details returned by the vault. +type TokenInfo struct { + Username string + Roles []string + IsAdmin bool +} + +// Login authenticates against the vault and returns the session token. +func (c *VaultClient) Login(ctx context.Context, username, password, totpCode string) (string, error) { + resp, err := c.auth.Login(ctx, &pb.LoginRequest{ + Username: username, + Password: password, + TotpCode: totpCode, + }) + if err != nil { + return "", err + } + return resp.Token, nil +} + +// ValidateToken validates a token against the vault and returns the token info. +func (c *VaultClient) ValidateToken(ctx context.Context, token string) (*TokenInfo, error) { + resp, err := c.auth.TokenInfo(withToken(ctx, token), &pb.TokenInfoRequest{}) + if err != nil { + return nil, err + } + return &TokenInfo{ + Username: resp.Username, + Roles: resp.Roles, + IsAdmin: resp.IsAdmin, + }, nil +} + +// MountInfo holds metadata about an engine mount. +type MountInfo struct { + Name string + Type string + MountPath string +} + +// ListMounts returns all engine mounts from the vault. +func (c *VaultClient) ListMounts(ctx context.Context, token string) ([]MountInfo, error) { + resp, err := c.engine.ListMounts(withToken(ctx, token), &pb.ListMountsRequest{}) + if err != nil { + return nil, err + } + mounts := make([]MountInfo, 0, len(resp.Mounts)) + for _, m := range resp.Mounts { + mounts = append(mounts, MountInfo{ + Name: m.Name, + Type: m.Type, + MountPath: m.MountPath, + }) + } + return mounts, nil +} + +// Mount creates a new engine mount on the vault. +func (c *VaultClient) Mount(ctx context.Context, token, name, engineType string, config map[string]interface{}) error { + req := &pb.MountRequest{ + Name: name, + Type: engineType, + } + if len(config) > 0 { + s, err := structFromMap(config) + if err != nil { + return fmt.Errorf("webserver: encode mount config: %w", err) + } + req.Config = s + } + _, err := c.engine.Mount(withToken(ctx, token), req) + return err +} + +// EngineRequest sends a generic engine operation to the vault. +func (c *VaultClient) EngineRequest(ctx context.Context, token, mount, operation string, data map[string]interface{}) (map[string]interface{}, error) { + req := &pb.EngineRequest{ + Mount: mount, + Operation: operation, + } + if len(data) > 0 { + s, err := structFromMap(data) + if err != nil { + return nil, fmt.Errorf("webserver: encode engine request: %w", err) + } + req.Data = s + } + resp, err := c.engine.Request(withToken(ctx, token), req) + if err != nil { + return nil, err + } + if resp.Data == nil { + return nil, nil + } + return resp.Data.AsMap(), nil +} + +// GetRootCert returns the root CA certificate PEM for the given mount. +func (c *VaultClient) GetRootCert(ctx context.Context, mount string) ([]byte, error) { + resp, err := c.pki.GetRootCert(ctx, &pb.GetRootCertRequest{Mount: mount}) + if err != nil { + return nil, err + } + return resp.CertPem, nil +} + +// GetIssuerCert returns a named issuer certificate PEM for the given mount. +func (c *VaultClient) GetIssuerCert(ctx context.Context, mount, issuer string) ([]byte, error) { + resp, err := c.pki.GetIssuerCert(ctx, &pb.GetIssuerCertRequest{Mount: mount, Issuer: issuer}) + if err != nil { + return nil, err + } + return resp.CertPem, nil +} diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go new file mode 100644 index 0000000..ded53ed --- /dev/null +++ b/internal/webserver/routes.go @@ -0,0 +1,430 @@ +package webserver + +import ( + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (ws *WebServer) registerRoutes(r chi.Router) { + r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(ws.staticFS)))) + + r.Get("/", ws.handleRoot) + r.HandleFunc("/init", ws.handleInit) + r.HandleFunc("/unseal", ws.handleUnseal) + r.HandleFunc("/login", ws.handleLogin) + r.Get("/dashboard", ws.requireAuth(ws.handleDashboard)) + r.Post("/dashboard/mount-ca", ws.requireAuth(ws.handleDashboardMountCA)) + + r.Route("/pki", func(r chi.Router) { + r.Get("/", ws.requireAuth(ws.handlePKI)) + r.Post("/import-root", ws.requireAuth(ws.handleImportRoot)) + r.Post("/create-issuer", ws.requireAuth(ws.handleCreateIssuer)) + r.Get("/{issuer}", ws.requireAuth(ws.handlePKIIssuer)) + }) +} + +// requireAuth validates the token cookie against the vault and injects TokenInfo. +func (ws *WebServer) requireAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + state, err := ws.vault.Status(r.Context()) + if err != nil || state != "unsealed" { + http.Redirect(w, r, "/", http.StatusFound) + return + } + token := extractCookie(r) + if token == "" { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + info, err := ws.vault.ValidateToken(r.Context(), token) + if err != nil { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + r = r.WithContext(withTokenInfo(r.Context(), info)) + next(w, r) + } +} + +func (ws *WebServer) handleRoot(w http.ResponseWriter, r *http.Request) { + state, err := ws.vault.Status(r.Context()) + if err != nil { + ws.renderTemplate(w, "unseal.html", map[string]interface{}{"Error": "Cannot reach vault"}) + return + } + switch state { + case "uninitialized", "initializing": + http.Redirect(w, r, "/init", http.StatusFound) + case "sealed": + http.Redirect(w, r, "/unseal", http.StatusFound) + case "unsealed": + http.Redirect(w, r, "/dashboard", http.StatusFound) + default: + http.Redirect(w, r, "/unseal", http.StatusFound) + } +} + +func (ws *WebServer) handleInit(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + state, _ := ws.vault.Status(r.Context()) + if state != "uninitialized" && state != "" { + http.Redirect(w, r, "/", http.StatusFound) + return + } + ws.renderTemplate(w, "init.html", nil) + case http.MethodPost: + r.ParseForm() + password := r.FormValue("password") + if password == "" { + ws.renderTemplate(w, "init.html", map[string]interface{}{"Error": "Password is required"}) + return + } + if err := ws.vault.Init(r.Context(), password); err != nil { + ws.renderTemplate(w, "init.html", map[string]interface{}{"Error": grpcMessage(err)}) + return + } + http.Redirect(w, r, "/dashboard", http.StatusFound) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } +} + +func (ws *WebServer) handleUnseal(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + state, _ := ws.vault.Status(r.Context()) + if state == "uninitialized" { + http.Redirect(w, r, "/init", http.StatusFound) + return + } + if state == "unsealed" { + http.Redirect(w, r, "/dashboard", http.StatusFound) + return + } + ws.renderTemplate(w, "unseal.html", nil) + case http.MethodPost: + r.ParseForm() + password := r.FormValue("password") + if err := ws.vault.Unseal(r.Context(), password); err != nil { + msg := "Invalid password" + if st, ok := status.FromError(err); ok && st.Code() == codes.ResourceExhausted { + msg = "Too many attempts. Please wait 60 seconds." + } + ws.renderTemplate(w, "unseal.html", map[string]interface{}{"Error": msg}) + return + } + http.Redirect(w, r, "/dashboard", http.StatusFound) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } +} + +func (ws *WebServer) handleLogin(w http.ResponseWriter, r *http.Request) { + state, _ := ws.vault.Status(r.Context()) + if state != "unsealed" { + http.Redirect(w, r, "/", http.StatusFound) + return + } + switch r.Method { + case http.MethodGet: + ws.renderTemplate(w, "login.html", nil) + case http.MethodPost: + r.ParseForm() + token, err := ws.vault.Login(r.Context(), + r.FormValue("username"), + r.FormValue("password"), + r.FormValue("totp_code"), + ) + if err != nil { + ws.renderTemplate(w, "login.html", map[string]interface{}{"Error": "Invalid credentials"}) + return + } + http.SetCookie(w, &http.Cookie{ + Name: "metacrypt_token", + Value: token, + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + http.Redirect(w, r, "/dashboard", http.StatusFound) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } +} + +func (ws *WebServer) handleDashboard(w http.ResponseWriter, r *http.Request) { + info := tokenInfoFromContext(r.Context()) + token := extractCookie(r) + mounts, _ := ws.vault.ListMounts(r.Context(), token) + state, _ := ws.vault.Status(r.Context()) + ws.renderTemplate(w, "dashboard.html", map[string]interface{}{ + "Username": info.Username, + "IsAdmin": info.IsAdmin, + "Roles": info.Roles, + "Mounts": mounts, + "State": state, + }) +} + +func (ws *WebServer) handleDashboardMountCA(w http.ResponseWriter, r *http.Request) { + info := tokenInfoFromContext(r.Context()) + if !info.IsAdmin { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + + if err := r.ParseMultipartForm(1 << 20); err != nil { + r.ParseForm() + } + + mountName := r.FormValue("name") + if mountName == "" { + ws.renderDashboardWithError(w, r, info, "Mount name is required") + return + } + + cfg := map[string]interface{}{} + if org := r.FormValue("organization"); org != "" { + cfg["organization"] = org + } + + var certPEM, keyPEM string + if f, _, err := r.FormFile("cert_file"); err == nil { + defer f.Close() + data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) + certPEM = string(data) + } + if f, _, err := r.FormFile("key_file"); err == nil { + defer f.Close() + data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) + keyPEM = string(data) + } + if certPEM != "" && keyPEM != "" { + cfg["root_cert_pem"] = certPEM + cfg["root_key_pem"] = keyPEM + } + + token := extractCookie(r) + if err := ws.vault.Mount(r.Context(), token, mountName, "ca", cfg); err != nil { + ws.renderDashboardWithError(w, r, info, grpcMessage(err)) + return + } + http.Redirect(w, r, "/pki", http.StatusFound) +} + +func (ws *WebServer) renderDashboardWithError(w http.ResponseWriter, r *http.Request, info *TokenInfo, errMsg string) { + token := extractCookie(r) + mounts, _ := ws.vault.ListMounts(r.Context(), token) + state, _ := ws.vault.Status(r.Context()) + ws.renderTemplate(w, "dashboard.html", map[string]interface{}{ + "Username": info.Username, + "IsAdmin": info.IsAdmin, + "Roles": info.Roles, + "Mounts": mounts, + "State": state, + "MountError": errMsg, + }) +} + +func (ws *WebServer) handlePKI(w http.ResponseWriter, r *http.Request) { + info := tokenInfoFromContext(r.Context()) + token := extractCookie(r) + + mountName, err := ws.findCAMount(r, token) + if err != nil { + http.Redirect(w, r, "/dashboard", http.StatusFound) + return + } + + data := map[string]interface{}{ + "Username": info.Username, + "IsAdmin": info.IsAdmin, + "MountName": mountName, + } + + if rootPEM, err := ws.vault.GetRootCert(r.Context(), mountName); err == nil && len(rootPEM) > 0 { + if cert, err := parsePEMCert(rootPEM); err == nil { + data["RootCN"] = cert.Subject.CommonName + data["RootOrg"] = strings.Join(cert.Subject.Organization, ", ") + data["RootNotBefore"] = cert.NotBefore.Format(time.RFC3339) + data["RootNotAfter"] = cert.NotAfter.Format(time.RFC3339) + data["RootExpired"] = time.Now().After(cert.NotAfter) + data["HasRoot"] = true + } + } + + if resp, err := ws.vault.EngineRequest(r.Context(), token, mountName, "list-issuers", nil); err == nil { + data["Issuers"] = resp["issuers"] + } + + ws.renderTemplate(w, "pki.html", data) +} + +func (ws *WebServer) handleImportRoot(w http.ResponseWriter, r *http.Request) { + info := tokenInfoFromContext(r.Context()) + if !info.IsAdmin { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + + token := extractCookie(r) + mountName, err := ws.findCAMount(r, token) + if err != nil { + http.Error(w, "no CA engine mounted", http.StatusNotFound) + return + } + + if err := r.ParseMultipartForm(1 << 20); err != nil { + r.ParseForm() + } + + certPEM := r.FormValue("cert_pem") + keyPEM := r.FormValue("key_pem") + if certPEM == "" { + if f, _, err := r.FormFile("cert_file"); err == nil { + defer f.Close() + data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) + certPEM = string(data) + } + } + if keyPEM == "" { + if f, _, err := r.FormFile("key_file"); err == nil { + defer f.Close() + data, _ := io.ReadAll(io.LimitReader(f, 1<<20)) + keyPEM = string(data) + } + } + + if certPEM == "" || keyPEM == "" { + ws.renderPKIWithError(w, r, mountName, info, "Certificate and private key are required") + return + } + + _, err = ws.vault.EngineRequest(r.Context(), token, mountName, "import-root", map[string]interface{}{ + "cert_pem": certPEM, + "key_pem": keyPEM, + }) + if err != nil { + ws.renderPKIWithError(w, r, mountName, info, grpcMessage(err)) + return + } + http.Redirect(w, r, "/pki", http.StatusFound) +} + +func (ws *WebServer) handleCreateIssuer(w http.ResponseWriter, r *http.Request) { + info := tokenInfoFromContext(r.Context()) + if !info.IsAdmin { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + + token := extractCookie(r) + mountName, err := ws.findCAMount(r, token) + if err != nil { + http.Error(w, "no CA engine mounted", http.StatusNotFound) + return + } + + r.ParseForm() + name := r.FormValue("name") + if name == "" { + ws.renderPKIWithError(w, r, mountName, info, "Issuer name is required") + return + } + + reqData := map[string]interface{}{"name": name} + if v := r.FormValue("expiry"); v != "" { + reqData["expiry"] = v + } + if v := r.FormValue("max_ttl"); v != "" { + reqData["max_ttl"] = v + } + if v := r.FormValue("key_algorithm"); v != "" { + reqData["key_algorithm"] = v + } + if v := r.FormValue("key_size"); v != "" { + var size float64 + if _, err := fmt.Sscanf(v, "%f", &size); err == nil { + reqData["key_size"] = size + } + } + + _, err = ws.vault.EngineRequest(r.Context(), token, mountName, "create-issuer", reqData) + if err != nil { + ws.renderPKIWithError(w, r, mountName, info, grpcMessage(err)) + return + } + http.Redirect(w, r, "/pki", http.StatusFound) +} + +func (ws *WebServer) handlePKIIssuer(w http.ResponseWriter, r *http.Request) { + token := extractCookie(r) + mountName, err := ws.findCAMount(r, token) + if err != nil { + http.Error(w, "no CA engine mounted", http.StatusNotFound) + return + } + + issuerName := chi.URLParam(r, "issuer") + certPEM, err := ws.vault.GetIssuerCert(r.Context(), mountName, issuerName) + if err != nil { + http.Error(w, "issuer not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/x-pem-file") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.pem", issuerName)) + w.Write(certPEM) +} + +func (ws *WebServer) renderPKIWithError(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, errMsg string) { + data := map[string]interface{}{ + "Username": info.Username, + "IsAdmin": info.IsAdmin, + "MountName": mountName, + "Error": errMsg, + } + + if rootPEM, err := ws.vault.GetRootCert(r.Context(), mountName); err == nil && len(rootPEM) > 0 { + if cert, err := parsePEMCert(rootPEM); err == nil { + data["RootCN"] = cert.Subject.CommonName + data["RootOrg"] = strings.Join(cert.Subject.Organization, ", ") + data["RootNotBefore"] = cert.NotBefore.Format(time.RFC3339) + data["RootNotAfter"] = cert.NotAfter.Format(time.RFC3339) + data["RootExpired"] = time.Now().After(cert.NotAfter) + data["HasRoot"] = true + } + } + + ws.renderTemplate(w, "pki.html", data) +} + +func (ws *WebServer) findCAMount(r *http.Request, token string) (string, error) { + mounts, err := ws.vault.ListMounts(r.Context(), token) + if err != nil { + return "", err + } + for _, m := range mounts { + if m.Type == "ca" { + return m.Name, nil + } + } + return "", fmt.Errorf("no CA engine mounted") +} + +// grpcMessage extracts a human-readable message from a gRPC error. +func grpcMessage(err error) string { + if st, ok := status.FromError(err); ok { + return st.Message() + } + return err.Error() +} diff --git a/internal/webserver/server.go b/internal/webserver/server.go new file mode 100644 index 0000000..d4a298a --- /dev/null +++ b/internal/webserver/server.go @@ -0,0 +1,112 @@ +// Package webserver implements the standalone web UI server for Metacrypt. +// It communicates with the vault over gRPC and renders server-side HTML. +package webserver + +import ( + "context" + "crypto/tls" + "fmt" + "html/template" + "io/fs" + "log/slog" + "net/http" + "time" + + "github.com/go-chi/chi/v5" + + webui "git.wntrmute.dev/kyle/metacrypt/web" + "git.wntrmute.dev/kyle/metacrypt/internal/config" +) + +// WebServer is the standalone web UI server. +type WebServer struct { + cfg *config.Config + vault *VaultClient + logger *slog.Logger + httpSrv *http.Server + staticFS fs.FS +} + +// New creates a new WebServer. It dials the vault gRPC endpoint. +func New(cfg *config.Config, logger *slog.Logger) (*WebServer, error) { + vault, err := NewVaultClient(cfg.Web.VaultGRPC, cfg.Web.VaultCACert) + if err != nil { + return nil, fmt.Errorf("webserver: connect to vault: %w", err) + } + + staticFS, err := fs.Sub(webui.FS, "static") + if err != nil { + return nil, fmt.Errorf("webserver: static FS: %w", err) + } + + return &WebServer{ + cfg: cfg, + vault: vault, + logger: logger, + staticFS: staticFS, + }, nil +} + +// Start starts the web server. It blocks until the server is closed. +func (ws *WebServer) Start() error { + r := chi.NewRouter() + ws.registerRoutes(r) + + ws.httpSrv = &http.Server{ + Addr: ws.cfg.Web.ListenAddr, + Handler: r, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + ws.logger.Info("starting web server", "addr", ws.cfg.Web.ListenAddr) + + if ws.cfg.Web.TLSCert != "" && ws.cfg.Web.TLSKey != "" { + ws.httpSrv.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} + err := ws.httpSrv.ListenAndServeTLS(ws.cfg.Web.TLSCert, ws.cfg.Web.TLSKey) + if err != nil && err != http.ErrServerClosed { + return fmt.Errorf("webserver: %w", err) + } + return nil + } + + err := ws.httpSrv.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return fmt.Errorf("webserver: %w", err) + } + return nil +} + +// Shutdown gracefully shuts down the web server. +func (ws *WebServer) Shutdown(ctx context.Context) error { + _ = ws.vault.Close() + if ws.httpSrv != nil { + return ws.httpSrv.Shutdown(ctx) + } + return nil +} + +func (ws *WebServer) renderTemplate(w http.ResponseWriter, name string, data interface{}) { + tmpl, err := template.ParseFS(webui.FS, + "templates/layout.html", + "templates/"+name, + ) + if err != nil { + ws.logger.Error("parse template", "name", name, "error", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := tmpl.ExecuteTemplate(w, "layout", data); err != nil { + ws.logger.Error("execute template", "name", name, "error", err) + } +} + +func extractCookie(r *http.Request) string { + c, err := r.Cookie("metacrypt_token") + if err != nil { + return "" + } + return c.Value +} diff --git a/internal/webserver/util.go b/internal/webserver/util.go new file mode 100644 index 0000000..7270326 --- /dev/null +++ b/internal/webserver/util.go @@ -0,0 +1,37 @@ +package webserver + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + + "google.golang.org/protobuf/types/known/structpb" +) + +type contextKey int + +const tokenInfoCtxKey contextKey = iota + +func withTokenInfo(ctx context.Context, info *TokenInfo) context.Context { + return context.WithValue(ctx, tokenInfoCtxKey, info) +} + +func tokenInfoFromContext(ctx context.Context) *TokenInfo { + v, _ := ctx.Value(tokenInfoCtxKey).(*TokenInfo) + return v +} + +// structFromMap converts a map[string]interface{} to a *structpb.Struct. +func structFromMap(m map[string]interface{}) (*structpb.Struct, error) { + return structpb.NewStruct(m) +} + +// parsePEMCert decodes the first PEM block and parses it as an x509 certificate. +func parsePEMCert(pemData []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(pemData) + if block == nil { + return nil, errors.New("no PEM block found") + } + return x509.ParseCertificate(block.Bytes) +} diff --git a/web/embed.go b/web/embed.go new file mode 100644 index 0000000..a3823a3 --- /dev/null +++ b/web/embed.go @@ -0,0 +1,9 @@ +// Package webui provides the embedded web UI assets (templates and static files). +package webui + +import "embed" + +// FS contains all web UI templates and static files. +// +//go:embed templates static +var FS embed.FS