From 8ecac00344e4f3673e6023544c334a03ce1c55f2 Mon Sep 17 00:00:00 2001 From: Jakub Coufal Date: Mon, 22 Jan 2024 16:24:22 +0100 Subject: [PATCH 1/2] add: deprecate tables.names and tables.delete --- cmd/leader.go | 4 +-- docs/changelog.md | 16 ++++++++++-- .../deploying_to_kubernetes.md | 3 --- docs/quickstart.md | 25 ++++++++----------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cmd/leader.go b/cmd/leader.go index 73c27310..eaabdd51 100644 --- a/cmd/leader.go +++ b/cmd/leader.go @@ -45,8 +45,8 @@ func init() { // Tables flags leaderCmd.PersistentFlags().StringSlice("tables.names", nil, "Create Regatta tables with given names.") leaderCmd.PersistentFlags().StringSlice("tables.delete", nil, "Delete Regatta tables with given names.") - _ = leaderCmd.PersistentFlags().MarkHidden("tables.names") - _ = leaderCmd.PersistentFlags().MarkHidden("tables.delete") + _ = leaderCmd.PersistentFlags().MarkDeprecated("tables.names", "Use `regatta.v1.Tables/Create` API to create tables instead.") + _ = leaderCmd.PersistentFlags().MarkDeprecated("tables.delete", "Use `regatta.v1.Tables/Delete` API to delete tables instead.") // Replication flags leaderCmd.PersistentFlags().Bool("replication.enabled", true, "Whether replication API is enabled.") diff --git a/docs/changelog.md b/docs/changelog.md index 0da7f673..dcbeac48 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,8 +5,21 @@ nav_order: 999 --- # Changelog +## v0.5.0 (unreleased) -## v0.4.1 (unreleased) +### Highlights +#### Dynamic tables management +* Tables now could be managed dynamically during the runtime of the server using newly provided `regatta.v1.Tables` API. +* Tables API could be secured by an API token using `tables.token` configuration value. + +### Deprecations +* `tables.names` and `tables.delete` configuration values were deprecated and will be removed in future releases. + +### Bugfixes +* Proper authentication of `maintenance.v1.Backup/Restore` API endpoint. +* When table is deleted in the leader cluster the followers will gracefully handle the situation by deleting the table locally and stopping the replication. +--- +## v0.4.1 ### Improvements * Server now reports transient errors for requests that could be potentially retried as `codes.Unavailable`. @@ -14,7 +27,6 @@ nav_order: 999 ### Bugfixes * Fixed the default timeout of `KV/IterateRange` operation. - --- ## v0.4.0 diff --git a/docs/operations_guide/deploying_to_kubernetes.md b/docs/operations_guide/deploying_to_kubernetes.md index 243cf8a6..5590dba8 100644 --- a/docs/operations_guide/deploying_to_kubernetes.md +++ b/docs/operations_guide/deploying_to_kubernetes.md @@ -63,9 +63,6 @@ To deploy Regatta follower cluster and connect it to the leader cluster, let's s mode: follower replicas: 3 -# Specify the tables. -tables: testing-table1,testing-table2 - replication: # Specify the address of the Regatta leader cluster to asynchronously replicate data from. leaderAddress: "leader.regatta.example.com:8444" diff --git a/docs/quickstart.md b/docs/quickstart.md index 12db1346..0b288620 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -39,16 +39,14 @@ tar -xf regatta-darwin-amd64.tar ./regatta leader \ --dev-mode \ --raft.address=127.0.0.1:5012 \ - --raft.initial-members='1=127.0.0.1:5012' \ - --tables.names=regatta-test + --raft.initial-members='1=127.0.0.1:5012' ``` - This command will start a Regatta leader cluster with a single instance locally. -{: .note } -Mind the flags providing certificates and keys for the APIs. For testing purposes, -[certificate and key present in the repository](https://github.com/jamf/regatta/tree/cfc58f0205484b0c8a24c7cbcc0be8563b7cf6a5/hack) -can be used. +Create the `regatta-test` table using the API. +```bash +grpcurl -plaintext -d "{\"name\": \"regatta-test\"}" 127.0.0.1:8443 regatta.v1.Tables/Create +``` ## Pull and run official Docker image @@ -58,21 +56,20 @@ Just execute `docker run` with the following arguments: ```bash docker run \ + -p 8443:8443 \ ghcr.io/jamf/regatta:latest \ leader \ --dev-mode \ --raft.address=127.0.0.1:5012 \ - --raft.initial-members='1=127.0.0.1:5012' \ - --tables.names=regatta-test + --raft.initial-members='1=127.0.0.1:5012' ``` This command will start a Regatta leader cluster with a single instance in a Docker container. -{: .note } -Mind the `--mount` argument mounting the `/hack` directory to the container. This is the default location -where Regatta looks for certificates and keys for APIs. For testing purposes, -[certificate and key present in the repository](https://github.com/jamf/regatta/tree/cfc58f0205484b0c8a24c7cbcc0be8563b7cf6a5/hack) -can be used. +Create the `regatta-test` table using the API. +```bash +grpcurl -plaintext -d "{\"name\": \"regatta-test\"}" 127.0.0.1:8443 regatta.v1.Tables/Create +``` ## Deploy to Kubernetes from Helm Chart From 84afc1aaf47fef4f5aa7dbd66225a47cd122acc3 Mon Sep 17 00:00:00 2001 From: Jakub Coufal Date: Mon, 22 Jan 2024 16:24:43 +0100 Subject: [PATCH 2/2] add: improve server auth --- cmd/common.go | 13 +++++++++++-- regattaserver/maintenance.go | 17 +++++++++-------- regattaserver/tables.go | 16 ++++------------ regattaserver/tables_test.go | 5 ----- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/cmd/common.go b/cmd/common.go index 843a3299..6fb57e51 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -14,6 +14,7 @@ import ( "sync" "time" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/jamf/regatta/cert" @@ -41,8 +42,14 @@ func createAPIServer() (*regattaserver.RegattaServer, error) { addr, secure, net := resolveUrl(viper.GetString("api.address")) opts := []grpc.ServerOption{ grpc.KeepaliveParams(keepalive.ServerParameters{MaxConnectionAge: 60 * time.Second}), - grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), - grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( + grpc_auth.StreamServerInterceptor(defaultAuthFunc), + grpc_prometheus.StreamServerInterceptor, + )), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( + grpc_prometheus.UnaryServerInterceptor, + grpc_auth.UnaryServerInterceptor(defaultAuthFunc), + )), } if secure { c, err := cert.New(viper.GetString("api.cert-filename"), viper.GetString("api.key-filename")) @@ -104,6 +111,8 @@ func authFunc(token string) func(ctx context.Context) (context.Context, error) { } } +var defaultAuthFunc = authFunc("") + type tokenCredentials string func (t tokenCredentials) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { diff --git a/regattaserver/maintenance.go b/regattaserver/maintenance.go index 5b0e0604..f225df17 100644 --- a/regattaserver/maintenance.go +++ b/regattaserver/maintenance.go @@ -25,10 +25,6 @@ type ResetServer struct { } func (m *ResetServer) Reset(ctx context.Context, req *regattapb.ResetRequest) (*regattapb.ResetResponse, error) { - ctx, err := m.AuthFunc(ctx) - if err != nil { - return nil, err - } reset := func(name string) error { t, err := m.Tables.GetTable(name) if err != nil { @@ -64,6 +60,10 @@ func (m *ResetServer) Reset(ctx context.Context, req *regattapb.ResetRequest) (* return ®attapb.ResetResponse{}, nil } +func (m *ResetServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) { + return m.AuthFunc(ctx) +} + // BackupServer implements some Maintenance service methods from proto/regatta.proto. type BackupServer struct { regattapb.UnimplementedMaintenanceServer @@ -72,10 +72,7 @@ type BackupServer struct { } func (m *BackupServer) Backup(req *regattapb.BackupRequest, srv regattapb.Maintenance_BackupServer) error { - ctx, err := m.AuthFunc(srv.Context()) - if err != nil { - return err - } + ctx := srv.Context() table, err := m.Tables.GetTable(string(req.Table)) if err != nil { return err @@ -149,6 +146,10 @@ func (m *BackupServer) Restore(srv regattapb.Maintenance_RestoreServer) error { return srv.SendAndClose(®attapb.RestoreResponse{}) } +func (m *BackupServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) { + return m.AuthFunc(ctx) +} + type backupReader struct { stream regattapb.Maintenance_RestoreServer } diff --git a/regattaserver/tables.go b/regattaserver/tables.go index 71480cf2..63426dee 100644 --- a/regattaserver/tables.go +++ b/regattaserver/tables.go @@ -22,10 +22,6 @@ type TablesServer struct { } func (t *TablesServer) Create(ctx context.Context, req *regattapb.CreateTableRequest) (*regattapb.CreateTableResponse, error) { - _, err := t.AuthFunc(ctx) - if err != nil { - return nil, err - } if len(req.Name) == 0 { return nil, status.Errorf(codes.InvalidArgument, "name must be set") } @@ -40,10 +36,6 @@ func (t *TablesServer) Create(ctx context.Context, req *regattapb.CreateTableReq } func (t *TablesServer) Delete(ctx context.Context, req *regattapb.DeleteTableRequest) (*regattapb.DeleteTableResponse, error) { - _, err := t.AuthFunc(ctx) - if err != nil { - return nil, err - } if len(req.Name) == 0 { return nil, status.Errorf(codes.InvalidArgument, "name must be set") } @@ -57,10 +49,6 @@ func (t *TablesServer) Delete(ctx context.Context, req *regattapb.DeleteTableReq } func (t *TablesServer) List(ctx context.Context, _ *regattapb.ListTablesRequest) (*regattapb.ListTablesResponse, error) { - _, err := t.AuthFunc(ctx) - if err != nil { - return nil, err - } ts, err := t.Tables.GetTables() if err != nil { if serrors.IsSafeToRetry(err) { @@ -81,6 +69,10 @@ func (t *TablesServer) List(ctx context.Context, _ *regattapb.ListTablesRequest) return resp, nil } +func (t *TablesServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) { + return t.AuthFunc(ctx) +} + type ReadonlyTablesServer struct { TablesServer } diff --git a/regattaserver/tables_test.go b/regattaserver/tables_test.go index fc9ad512..ba36a188 100644 --- a/regattaserver/tables_test.go +++ b/regattaserver/tables_test.go @@ -153,11 +153,6 @@ func TestTablesServer_List(t *testing.T) { }, }}, }, - { - name: "deny all", - fields: fields{AuthFunc: denyAll}, - wantErr: require.Error, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {