-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement CLI API Endpoints for CAPI Clustering (#498)
* Implement API/CLI for CAPI clustering --------- Co-authored-by: Benjamin Schimke <benjamin.schimke@canonical.com>
- Loading branch information
1 parent
15c26a5
commit 9a96ad3
Showing
14 changed files
with
310 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package v1 | ||
|
||
// SetClusterAPIAuthTokenRequest is used to request to set the auth token for ClusterAPI. | ||
type SetClusterAPIAuthTokenRequest struct { | ||
Token string `json:"token"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package k8s | ||
|
||
import ( | ||
apiv1 "github.com/canonical/k8s/api/v1" | ||
cmdutil "github.com/canonical/k8s/cmd/util" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func newXCAPICmd(env cmdutil.ExecutionEnvironment) *cobra.Command { | ||
setAuthTokenCmd := &cobra.Command{ | ||
Use: "set-auth-token <token>", | ||
Short: "Set the auth token for the CAPI provider", | ||
Args: cmdutil.ExactArgs(env, 1), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
token := args[0] | ||
if token == "" { | ||
cmd.PrintErrf("Error: The token must be provided.\n") | ||
env.Exit(1) | ||
return | ||
} | ||
|
||
client, err := env.Client(cmd.Context()) | ||
if err != nil { | ||
cmd.PrintErrf("Error: Failed to create a k8sd client. Make sure that the k8sd service is running.\n\nThe error was: %v\n", err) | ||
env.Exit(1) | ||
return | ||
} | ||
|
||
err = client.SetClusterAPIAuthToken(cmd.Context(), apiv1.SetClusterAPIAuthTokenRequest{Token: token}) | ||
if err != nil { | ||
cmd.PrintErrf("Error: Failed to set the CAPI auth token.\n\nThe error was: %v\n", err) | ||
env.Exit(1) | ||
return | ||
} | ||
}, | ||
} | ||
|
||
cmd := &cobra.Command{ | ||
Use: "x-capi", | ||
Short: "Manage the CAPI integration", | ||
Hidden: true, | ||
} | ||
|
||
cmd.AddCommand(setAuthTokenCmd) | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
apiv1 "github.com/canonical/k8s/api/v1" | ||
"github.com/canonical/lxd/shared/api" | ||
) | ||
|
||
// SetClusterAPIAuthToken calls "POST 1.0/x/capi/set-auth-token". | ||
func (c *k8sdClient) SetClusterAPIAuthToken(ctx context.Context, request apiv1.SetClusterAPIAuthTokenRequest) error { | ||
if err := c.mc.Query(ctx, "POST", api.NewURL().Path("x", "capi", "set-auth-token"), request, nil); err != nil { | ||
return fmt.Errorf("failed to POST /x/capi/set-auth-token: %w", err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/canonical/k8s/pkg/k8sd/database" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/state" | ||
) | ||
|
||
func ValidateCAPIAuthTokenAccessHandler(tokenHeaderName string) func(s *state.State, r *http.Request) response.Response { | ||
return func(s *state.State, r *http.Request) response.Response { | ||
token := r.Header.Get(tokenHeaderName) | ||
if token == "" { | ||
return response.Unauthorized(fmt.Errorf("missing header %q", tokenHeaderName)) | ||
} | ||
|
||
var tokenIsValid bool | ||
if err := s.Database.Transaction(s.Context, func(ctx context.Context, tx *sql.Tx) error { | ||
var err error | ||
tokenIsValid, err = database.ValidateClusterAPIToken(ctx, tx, token) | ||
if err != nil { | ||
return fmt.Errorf("failed to check CAPI auth token: %w", err) | ||
} | ||
return nil | ||
}); err != nil { | ||
return response.InternalError(fmt.Errorf("check CAPI auth token database transaction failed: %w", err)) | ||
} | ||
if !tokenIsValid { | ||
return response.Unauthorized(fmt.Errorf("invalid token")) | ||
} | ||
|
||
return response.EmptySyncResponse | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
|
||
apiv1 "github.com/canonical/k8s/api/v1" | ||
"github.com/canonical/k8s/pkg/k8sd/database" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/state" | ||
) | ||
|
||
func (e *Endpoints) postSetClusterAPIAuthToken(s *state.State, r *http.Request) response.Response { | ||
request := apiv1.SetClusterAPIAuthTokenRequest{} | ||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | ||
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err)) | ||
} | ||
|
||
if err := s.Database.Transaction(r.Context(), func(ctx context.Context, tx *sql.Tx) error { | ||
return database.SetClusterAPIToken(ctx, tx, request.Token) | ||
}); err != nil { | ||
return response.InternalError(err) | ||
} | ||
|
||
return response.SyncResponse(true, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
|
||
"github.com/canonical/microcluster/cluster" | ||
) | ||
|
||
var ( | ||
clusterAPIConfigsStmts = map[string]int{ | ||
"insert-capi-token": MustPrepareStatement("cluster-configs", "insert-capi-token.sql"), | ||
"select-capi-token": MustPrepareStatement("cluster-configs", "select-capi-token.sql"), | ||
} | ||
) | ||
|
||
// SetClusterAPIToken stores the ClusterAPI token in the cluster config. | ||
func SetClusterAPIToken(ctx context.Context, tx *sql.Tx, token string) error { | ||
if token == "" { | ||
return fmt.Errorf("token cannot be empty") | ||
} | ||
|
||
insertTxStmt, err := cluster.Stmt(tx, clusterAPIConfigsStmts["insert-capi-token"]) | ||
if err != nil { | ||
return fmt.Errorf("failed to prepare insert statement: %w", err) | ||
} | ||
if _, err := insertTxStmt.ExecContext(ctx, token); err != nil { | ||
return fmt.Errorf("insert ClusterAPI token query failed: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ValidateClusterAPIToken returns true if the specified token matches the stored ClusterAPI token. | ||
func ValidateClusterAPIToken(ctx context.Context, tx *sql.Tx, token string) (bool, error) { | ||
selectTxStmt, err := cluster.Stmt(tx, clusterAPIConfigsStmts["select-capi-token"]) | ||
if err != nil { | ||
return false, fmt.Errorf("failed to prepare select statement: %w", err) | ||
} | ||
|
||
var exists bool | ||
err = selectTxStmt.QueryRowContext(ctx, token).Scan(&exists) | ||
if err != nil { | ||
return false, fmt.Errorf("failed to query ClusterAPI token: %w", err) | ||
} | ||
|
||
return exists, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package database_test | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"testing" | ||
|
||
"github.com/canonical/k8s/pkg/k8sd/database" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestClusterAPIAuthTokens(t *testing.T) { | ||
WithDB(t, func(ctx context.Context, db DB) { | ||
var token string = "test-token" | ||
|
||
t.Run("SetAuthToken", func(t *testing.T) { | ||
g := NewWithT(t) | ||
err := db.Transaction(ctx, func(ctx context.Context, tx *sql.Tx) error { | ||
err := database.SetClusterAPIToken(ctx, tx, token) | ||
g.Expect(err).To(BeNil()) | ||
return nil | ||
}) | ||
g.Expect(err).To(BeNil()) | ||
}) | ||
|
||
t.Run("CheckAuthToken", func(t *testing.T) { | ||
t.Run("ValidToken", func(t *testing.T) { | ||
g := NewWithT(t) | ||
err := db.Transaction(ctx, func(ctx context.Context, tx *sql.Tx) error { | ||
valid, err := database.ValidateClusterAPIToken(ctx, tx, token) | ||
g.Expect(err).To(BeNil()) | ||
g.Expect(valid).To(BeTrue()) | ||
return nil | ||
}) | ||
g.Expect(err).To(BeNil()) | ||
}) | ||
|
||
t.Run("InvalidToken", func(t *testing.T) { | ||
g := NewWithT(t) | ||
err := db.Transaction(ctx, func(ctx context.Context, tx *sql.Tx) error { | ||
valid, err := database.ValidateClusterAPIToken(ctx, tx, "invalid-token") | ||
g.Expect(err).To(BeNil()) | ||
g.Expect(valid).To(BeFalse()) | ||
return nil | ||
}) | ||
g.Expect(err).To(BeNil()) | ||
}) | ||
}) | ||
}) | ||
} |
7 changes: 7 additions & 0 deletions
7
src/k8s/pkg/k8sd/database/sql/queries/cluster-configs/insert-capi-token.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
INSERT INTO | ||
cluster_configs(key, value) | ||
VALUES | ||
("token::capi", ?) | ||
ON CONFLICT(key) DO | ||
UPDATE SET value = EXCLUDED.value; | ||
|
6 changes: 6 additions & 0 deletions
6
src/k8s/pkg/k8sd/database/sql/queries/cluster-configs/select-capi-token.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
SELECT | ||
EXISTS ( | ||
SELECT 1 | ||
FROM cluster_configs AS c | ||
WHERE c.key = 'token::capi' AND c.value = ? | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters