diff --git a/app/demo/cmd/main.go b/app/demo/cmd/main.go index 5870cfe..829bf1f 100644 --- a/app/demo/cmd/main.go +++ b/app/demo/cmd/main.go @@ -5,41 +5,34 @@ package main import ( - "context" "fmt" spike "github.com/spiffe/spike-sdk-go/api" - "github.com/spiffe/spike-sdk-go/spiffe" ) func main() { - // Create a context. - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + api := spike.New() // Use the default Workload API Socket + defer api.Close() // Close the connection when done - // Initialize the SPIFFE endpoint socket. - defaultEndpointSocket := spiffe.EndpointSocket() + path := "/tenants/demo/db/creds" + version := 0 - // Initialize the SPIFFE source. - source, spiffeid, err := spiffe.Source(ctx, defaultEndpointSocket) + // Create a Secret + err := api.PutSecret(path, map[string]string{ + "username": "SPIKE", + "password": "SPIKE_Rocks", + }) if err != nil { - fmt.Println(err.Error()) + fmt.Println("Error writing secret:", err.Error()) return } - // Close the SPIFFE source when done. - defer spiffe.CloseSource(source) - - fmt.Println("SPIFFE ID:", spiffeid) - - // - // Retrieve a secret using SPIKE SDK. - // - - path := "/tenants/demo/db/creds" - version := 0 + // TODO: maybe GetSecret should not have a `version` parameter + // and an override should be used instead for versioned secret gets. + // as in api.GetSecretVersioned(path, version) - secret, err := spike.GetSecret(source, path, version) + // Read the Secret + secret, err := api.GetSecret(path, version) if err != nil { fmt.Println("Error reading secret:", err.Error()) return diff --git a/app/keeper/cmd/main.go b/app/keeper/cmd/main.go index e0ca834..9af653a 100644 --- a/app/keeper/cmd/main.go +++ b/app/keeper/cmd/main.go @@ -8,6 +8,8 @@ import ( "context" "fmt" + "github.com/spiffe/spike-sdk-go/spiffe" + "github.com/spiffe/spike/app/keeper/internal/env" "github.com/spiffe/spike/app/keeper/internal/route/handle" "github.com/spiffe/spike/app/keeper/internal/trust" @@ -15,7 +17,6 @@ import ( "github.com/spiffe/spike/internal/config" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) const appName = "SPIKE Keeper" @@ -26,7 +27,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - source, spiffeid, err := spiffe.AppSpiffeSource(ctx) + source, spiffeid, err := spiffe.Source(ctx, spiffe.EndpointSocket()) if err != nil { log.FatalLn(err.Error()) } diff --git a/app/nexus/cmd/main.go b/app/nexus/cmd/main.go index 8760633..82c5b18 100644 --- a/app/nexus/cmd/main.go +++ b/app/nexus/cmd/main.go @@ -10,6 +10,8 @@ import ( "fmt" "time" + "github.com/spiffe/spike-sdk-go/spiffe" + "github.com/spiffe/spike/app/nexus/internal/env" "github.com/spiffe/spike/app/nexus/internal/poll" "github.com/spiffe/spike/app/nexus/internal/route/handle" @@ -19,7 +21,6 @@ import ( "github.com/spiffe/spike/internal/config" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) const appName = "SPIKE Nexus" @@ -30,7 +31,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - source, spiffeid, err := spiffe.AppSpiffeSource(ctx) + source, spiffeid, err := spiffe.Source(ctx, spiffe.EndpointSocket()) if err != nil { log.Fatal(err.Error()) } diff --git a/app/nexus/internal/route/acl/policy/create.go b/app/nexus/internal/route/acl/policy/create.go index f8249ee..e869e99 100644 --- a/app/nexus/internal/route/acl/policy/create.go +++ b/app/nexus/internal/route/acl/policy/create.go @@ -5,17 +5,17 @@ package policy import ( - "github.com/spiffe/spike-sdk-go/api/errors" "net/http" "time" "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" + "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RoutePutPolicy handles HTTP PUT requests for creating new policies. diff --git a/app/nexus/internal/route/acl/policy/delete.go b/app/nexus/internal/route/acl/policy/delete.go index 9b46c32..5055023 100644 --- a/app/nexus/internal/route/acl/policy/delete.go +++ b/app/nexus/internal/route/acl/policy/delete.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteDeletePolicy handles HTTP DELETE requests to remove existing policies. diff --git a/app/nexus/internal/route/acl/policy/list.go b/app/nexus/internal/route/acl/policy/list.go index ea7189c..4acebee 100644 --- a/app/nexus/internal/route/acl/policy/list.go +++ b/app/nexus/internal/route/acl/policy/list.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteListPolicies handles HTTP requests to retrieve all existing policies. diff --git a/app/nexus/internal/route/acl/policy/read.go b/app/nexus/internal/route/acl/policy/read.go index 50040df..8fbf4f8 100644 --- a/app/nexus/internal/route/acl/policy/read.go +++ b/app/nexus/internal/route/acl/policy/read.go @@ -11,11 +11,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" apiErr "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteGetPolicy handles HTTP requests to retrieve a specific policy by its ID. diff --git a/app/nexus/internal/route/auth/initialization/route.go b/app/nexus/internal/route/auth/initialization/route.go index 669c83d..cef23ff 100644 --- a/app/nexus/internal/route/auth/initialization/route.go +++ b/app/nexus/internal/route/auth/initialization/route.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteInitCheck handles HTTP requests to check the initialization state of diff --git a/app/nexus/internal/route/store/secret/delete.go b/app/nexus/internal/route/store/secret/delete.go index 1fd020a..cb94f92 100644 --- a/app/nexus/internal/route/store/secret/delete.go +++ b/app/nexus/internal/route/store/secret/delete.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteDeleteSecret handles HTTP DELETE requests for secret deletion diff --git a/app/nexus/internal/route/store/secret/errors.go b/app/nexus/internal/route/store/secret/errors.go new file mode 100644 index 0000000..cbfc848 --- /dev/null +++ b/app/nexus/internal/route/store/secret/errors.go @@ -0,0 +1,48 @@ +// \\ SPIKE: Secure your secrets with SPIFFE. +// \\\\\ Copyright 2024-present SPIKE contributors. +// \\\\\\\ SPDX-License-Identifier: Apache-2.0 + +package secret + +import ( + "errors" + "net/http" + + "github.com/spiffe/spike-sdk-go/api/entity/data" + "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" + + "github.com/spiffe/spike/internal/log" + "github.com/spiffe/spike/internal/net" + "github.com/spiffe/spike/pkg/store" +) + +func handleGetSecretError(err error, w http.ResponseWriter) error { + // TODO: maybe reuse this in getSecret too -- currently only getsecretmeta uses it. + + fName := "handleGetSecretError" + + if errors.Is(err, store.ErrSecretNotFound) { + log.Log().Info(fName, "msg", "Secret not found") + + res := reqres.SecretReadResponse{Err: data.ErrNotFound} + responseBody := net.MarshalBody(res, w) + if responseBody == nil { + return errors.New("failed to marshal response body") + } + + net.Respond(http.StatusNotFound, responseBody, w) + return nil + } + + log.Log().Info(fName, "msg", + "Failed to retrieve secret", "err", err) + responseBody := net.MarshalBody(reqres.SecretReadResponse{ + Err: "Internal server error"}, w, + ) + if responseBody == nil { + return errors.New("failed to marshal response body") + } + + net.Respond(http.StatusInternalServerError, responseBody, w) + return err +} diff --git a/app/nexus/internal/route/store/secret/get.go b/app/nexus/internal/route/store/secret/get.go index 5a42f64..eaacf16 100644 --- a/app/nexus/internal/route/store/secret/get.go +++ b/app/nexus/internal/route/store/secret/get.go @@ -11,11 +11,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" apiErr "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" "github.com/spiffe/spike/pkg/store" ) @@ -137,7 +137,9 @@ func RouteGetSecret( return err } - responseBody := net.MarshalBody(reqres.SecretReadResponse{Data: secret}, w) + responseBody := net.MarshalBody(reqres.SecretReadResponse{ + Secret: data.Secret{Data: secret}, + }, w) if responseBody == nil { return apiErr.ErrMarshalFailure } @@ -146,148 +148,3 @@ func RouteGetSecret( log.Log().Info("routeGetSecret", "msg", data.ErrSuccess) return nil } - -// RouteGetSecretMetadata handles requests to retrieve a secret metadata at a specific path -// and version. -// -// This endpoint requires a valid admin JWT token for authentication. The -// function retrieves a secret based on the provided path and optional version -// number. If no version is specified, the latest version is returned. -// -// The function follows these steps: -// 1. Validates the JWT token -// 2. Validates and unmarshal the request body -// 3. Attempts to retrieve the secret metadata -// 4. Returns the secret metadata or an appropriate error response -// -// Parameters: -// - w: http.ResponseWriter to write the HTTP response -// - r: *http.Request containing the incoming HTTP request -// - audit: *log.AuditEntry for logging audit information -// -// Returns: -// - error: if an error occurs during request processing. -// -// Request body format: -// -// { -// "path": string, // Path to the secret -// "version": int // Optional: specific version to retrieve -// } -// -// Response format on success (200 OK): -// -// "versions": { // map[int]SecretMetadataVersionResponse -// -// "version": { // SecretMetadataVersionResponse object -// "createdTime": "", // time.Time -// "version": 0, // int -// "deletedTime": null // *time.Time (pointer, can be null) -// } -// }, -// -// "metadata": { // SecretRawMetadataResponse object -// -// "currentVersion": 0, // int -// "oldestVersion": 0, // int -// "createdTime": "", // time.Time -// "updatedTime": "", // time.Time -// "maxVersions": 0 // int -// }, -// -// "err": null // ErrorCode -// -// Error responses: -// - 401 Unauthorized: Invalid or missing JWT token -// - 400 Bad Request: Invalid request body -// - 404 Not Found: Secret doesn't exist at specified path/version -// -// All operations are logged using structured logging. -func RouteGetSecretMetadata( - w http.ResponseWriter, r *http.Request, audit *log.AuditEntry, -) error { - log.Log().Info("routeGetSecretMetadata", "method", r.Method, "path", r.URL.Path, - "query", r.URL.RawQuery) - audit.Action = log.AuditRead - - requestBody := net.ReadRequestBody(w, r) - if requestBody == nil { - return errors.New("failed to read request body") - } - - request := net.HandleRequest[ - reqres.SecretReadRequest, reqres.SecretReadResponse]( - requestBody, w, - reqres.SecretReadResponse{Err: data.ErrBadInput}, - ) - if request == nil { - return errors.New("failed to parse request body") - } - - path := request.Path - version := request.Version - - rawSecret, err := state.GetRawSecret(path, version) - if err != nil { - return handleError(err, w) - } - - response := rawSecretResponseMapper(rawSecret) - responseBody := net.MarshalBody(response, w) - - if responseBody == nil { - return errors.New("failed to marshal response body") - } - - net.Respond(http.StatusOK, responseBody, w) - log.Log().Info("routeGetSecret", "msg", "OK") - return nil -} - -func rawSecretResponseMapper(rawSecret *store.Secret) reqres.SecretMetadataResponse { - versions := make(map[int]reqres.SecretMetadataVersionResponse) - for versionNum, version := range rawSecret.Versions { - versions[versionNum] = reqres.SecretMetadataVersionResponse{ - CreatedTime: version.CreatedTime, - Version: version.Version, - DeletedTime: version.DeletedTime, - } - } - - metadata := reqres.SecretRawMetadataResponse{ - CurrentVersion: rawSecret.Metadata.CurrentVersion, - OldestVersion: rawSecret.Metadata.OldestVersion, - CreatedTime: rawSecret.Metadata.CreatedTime, - UpdatedTime: rawSecret.Metadata.UpdatedTime, - MaxVersions: rawSecret.Metadata.MaxVersions, - } - - return reqres.SecretMetadataResponse{ - Versions: versions, - Metadata: metadata, - } -} - -func handleError(err error, w http.ResponseWriter) error { - if errors.Is(err, store.ErrSecretNotFound) { - log.Log().Info("routeGetSecret", "msg", "Secret not found") - - res := reqres.SecretReadResponse{Err: data.ErrNotFound} - responseBody := net.MarshalBody(res, w) - if responseBody == nil { - return errors.New("failed to marshal response body") - } - net.Respond(http.StatusNotFound, responseBody, w) - return nil - } - - log.Log().Info("routeGetSecret", "msg", "Failed to retrieve secret", "err", err) - responseBody := net.MarshalBody(reqres.SecretReadResponse{ - Err: "Internal server error"}, w, - ) - if responseBody == nil { - return errors.New("failed to marshal response body") - } - net.Respond(http.StatusInternalServerError, responseBody, w) - return err -} diff --git a/app/nexus/internal/route/store/secret/list.go b/app/nexus/internal/route/store/secret/list.go index 28c1b7c..e72dced 100644 --- a/app/nexus/internal/route/store/secret/list.go +++ b/app/nexus/internal/route/store/secret/list.go @@ -5,6 +5,7 @@ package secret import ( + "github.com/spiffe/spike-sdk-go/spiffe" "net/http" "github.com/spiffe/spike-sdk-go/api/entity/data" @@ -14,7 +15,6 @@ import ( state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteListPaths handles requests to retrieve all available secret paths. diff --git a/app/nexus/internal/route/store/secret/map.go b/app/nexus/internal/route/store/secret/map.go new file mode 100644 index 0000000..1ec0709 --- /dev/null +++ b/app/nexus/internal/route/store/secret/map.go @@ -0,0 +1,38 @@ +// \\ SPIKE: Secure your secrets with SPIFFE. +// \\\\\ Copyright 2024-present SPIKE contributors. +// \\\\\\\ SPDX-License-Identifier: Apache-2.0 + +package secret + +import ( + "github.com/spiffe/spike-sdk-go/api/entity/data" + "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" + + "github.com/spiffe/spike/pkg/store" +) + +func toSecretMetadataResponse( + secret *store.Secret, +) reqres.SecretMetadataResponse { + versions := make(map[int]data.SecretVersionInfo) + for _, version := range secret.Versions { + versions[version.Version] = data.SecretVersionInfo{ + CreatedTime: version.CreatedTime, + Version: version.Version, + DeletedTime: version.DeletedTime, + } + } + + return reqres.SecretMetadataResponse{ + SecretMetadata: data.SecretMetadata{ + Versions: versions, + Metadata: data.SecretMetaDataContent{ + CurrentVersion: secret.Metadata.CurrentVersion, + OldestVersion: secret.Metadata.OldestVersion, + CreatedTime: secret.Metadata.CreatedTime, + UpdatedTime: secret.Metadata.UpdatedTime, + MaxVersions: secret.Metadata.MaxVersions, + }, + }, + } +} diff --git a/app/nexus/internal/route/store/secret/metadata_get.go b/app/nexus/internal/route/store/secret/metadata_get.go new file mode 100644 index 0000000..ac6021c --- /dev/null +++ b/app/nexus/internal/route/store/secret/metadata_get.go @@ -0,0 +1,114 @@ +// \\ SPIKE: Secure your secrets with SPIFFE. +// \\\\\ Copyright 2024-present SPIKE contributors. +// \\\\\\\ SPDX-License-Identifier: Apache-2.0 + +package secret + +import ( + "errors" + "net/http" + + "github.com/spiffe/spike-sdk-go/api/entity/data" + "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" + + state "github.com/spiffe/spike/app/nexus/internal/state/base" + "github.com/spiffe/spike/internal/log" + "github.com/spiffe/spike/internal/net" +) + +// RouteGetSecretMetadata handles requests to retrieve a secret metadata at a +// specific path and version. +// +// This endpoint requires a valid admin JWT token for authentication. The +// function retrieves a secret based on the provided path and optional version +// number. If no version is specified, the latest version is returned. +// +// The function follows these steps: +// 1. Validates the JWT token +// 2. Validates and unmarshal the request body +// 3. Attempts to retrieve the secret metadata +// 4. Returns the secret metadata or an appropriate error response +// +// Parameters: +// - w: http.ResponseWriter to write the HTTP response +// - r: *http.Request containing the incoming HTTP request +// - audit: *log.AuditEntry for logging audit information +// +// Returns: +// - error: if an error occurs during request processing. +// +// Request body format: +// +// { +// "path": string, // Path to the secret +// "version": int // Optional: specific version to retrieve +// } +// +// Response format on success (200 OK): +// +// "versions": { // map[int]SecretMetadataVersionResponse +// +// "version": { // SecretMetadataVersionResponse object +// "createdTime": "", // time.Time +// "version": 0, // int +// "deletedTime": null // *time.Time (pointer, can be null) +// } +// }, +// +// "metadata": { // SecretRawMetadataResponse object +// +// "currentVersion": 0, // int +// "oldestVersion": 0, // int +// "createdTime": "", // time.Time +// "updatedTime": "", // time.Time +// "maxVersions": 0 // int +// }, +// +// "err": null // ErrorCode +// +// Error responses: +// - 401 Unauthorized: Invalid or missing JWT token +// - 400 Bad Request: Invalid request body +// - 404 Not Found: Secret doesn't exist at specified path/version +// +// All operations are logged using structured logging. +func RouteGetSecretMetadata( + w http.ResponseWriter, r *http.Request, audit *log.AuditEntry, +) error { + log.Log().Info("routeGetSecretMetadata", "method", r.Method, "path", r.URL.Path, + "query", r.URL.RawQuery) + audit.Action = log.AuditRead + + requestBody := net.ReadRequestBody(w, r) + if requestBody == nil { + return errors.New("failed to read request body") + } + + request := net.HandleRequest[ + reqres.SecretReadRequest, reqres.SecretReadResponse]( + requestBody, w, + reqres.SecretReadResponse{Err: data.ErrBadInput}, + ) + if request == nil { + return errors.New("failed to parse request body") + } + + path := request.Path + version := request.Version + + rawSecret, err := state.GetRawSecret(path, version) + if err != nil { + return handleGetSecretError(err, w) + } + + response := toSecretMetadataResponse(rawSecret) + responseBody := net.MarshalBody(response, w) + + if responseBody == nil { + return errors.New("failed to marshal response body") + } + + net.Respond(http.StatusOK, responseBody, w) + log.Log().Info("routeGetSecret", "msg", "OK") + return nil +} diff --git a/app/nexus/internal/route/store/secret/put.go b/app/nexus/internal/route/store/secret/put.go index 23686b0..4c6fa20 100644 --- a/app/nexus/internal/route/store/secret/put.go +++ b/app/nexus/internal/route/store/secret/put.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RoutePutSecret handles HTTP requests to create or update secrets at a diff --git a/app/nexus/internal/route/store/secret/undelete.go b/app/nexus/internal/route/store/secret/undelete.go index c851502..b5d6bf9 100644 --- a/app/nexus/internal/route/store/secret/undelete.go +++ b/app/nexus/internal/route/store/secret/undelete.go @@ -10,11 +10,11 @@ import ( "github.com/spiffe/spike-sdk-go/api/entity/data" "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" "github.com/spiffe/spike-sdk-go/api/errors" + "github.com/spiffe/spike-sdk-go/spiffe" state "github.com/spiffe/spike/app/nexus/internal/state/base" "github.com/spiffe/spike/internal/log" "github.com/spiffe/spike/internal/net" - "github.com/spiffe/spike/pkg/spiffe" ) // RouteUndeleteSecret handles HTTP requests to restore previously deleted diff --git a/app/spike/cmd/main.go b/app/spike/cmd/main.go index c636599..29dff24 100644 --- a/app/spike/cmd/main.go +++ b/app/spike/cmd/main.go @@ -7,17 +7,18 @@ package main import ( "context" + "github.com/spiffe/spike-sdk-go/spiffe" + "github.com/spiffe/spike/app/spike/internal/cmd" "github.com/spiffe/spike/app/spike/internal/trust" "github.com/spiffe/spike/internal/log" - "github.com/spiffe/spike/pkg/spiffe" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - source, spiffeid, err := spiffe.AppSpiffeSource(ctx) + source, spiffeid, err := spiffe.Source(ctx, spiffe.EndpointSocket()) if err != nil { log.Fatal(err.Error()) } diff --git a/app/spike/internal/cmd/secret/delete.go b/app/spike/internal/cmd/secret/delete.go index 493df77..8c21f65 100644 --- a/app/spike/internal/cmd/secret/delete.go +++ b/app/spike/internal/cmd/secret/delete.go @@ -97,7 +97,18 @@ Examples: } } - err = spike.DeleteSecret(source, path, versionList) + var vv []int + for _, v := range versionList { + iv, err := strconv.Atoi(v) + if err == nil { + vv = append(vv, iv) + } + } + if vv == nil { + vv = []int{} + } + + err = spike.DeleteSecret(source, path, vv) if err != nil { fmt.Printf("Error: %v\n", err) return diff --git a/app/spike/internal/cmd/secret/get.go b/app/spike/internal/cmd/secret/get.go index 89b79e7..d6cb17c 100644 --- a/app/spike/internal/cmd/secret/get.go +++ b/app/spike/internal/cmd/secret/get.go @@ -6,9 +6,6 @@ package secret import ( "fmt" - "github.com/spiffe/spike-sdk-go/api/entity/v1/reqres" - "strings" - "time" "github.com/spf13/cobra" "github.com/spiffe/go-spiffe/v2/workloadapi" @@ -85,116 +82,3 @@ func newSecretGetCommand(source *workloadapi.X509Source) *cobra.Command { return getCmd } - -// newSecretMetadataGetCommand creates and returns a new cobra.Command for retrieving -// secrets. It configures a command that fetches and displays secret data from a -// specified path. -// -// Parameters: -// - source: X.509 source for workload API authentication -// -// The command accepts a single argument: -// - path: Location of the secret to retrieve -// -// Flags: -// - --version, -v (int): Specific version of the secret to retrieve -// (default 0) where 0 represents the current version -// -// Returns: -// - *cobra.Command: Configured get command -// -// The command will: -// 1. Verify SPIKE initialization status via admin token -// 2. Retrieve the secret metadata from the specified path and version -// 3. Display all metadata fields and secret versions -// -// Error cases: -// - SPIKE not initialized: Prompts user to run 'spike init' -// - Secret not found: Displays appropriate message -// - Read errors: Displays error message -func newSecretMetadataGetCommand(source *workloadapi.X509Source) *cobra.Command { - getMetadataCmd := &cobra.Command{ - Use: "metadata", - Short: "Manage secrets", - } - var getCmd = &cobra.Command{ - Use: "get ", - Short: "Get secrets metadata from the specified path", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - state, err := spike.CheckInitState(source) - if err != nil { - fmt.Println("Failed to check initialization state:") - fmt.Println(err.Error()) - return - } - - if state == data.NotInitialized { - fmt.Println("Please initialize SPIKE first by running 'spike init'.") - return - } - - path := args[0] - version, _ := cmd.Flags().GetInt("version") - - secret, err := spike.GetSecretMetadata(source, path, version) - if err != nil { - fmt.Println("Error reading secret:", err.Error()) - return - } - - if secret == nil { - fmt.Println("Secret not found.") - return - } - - printSecretResponse(secret) - }, - } - - getCmd.Flags().IntP("version", "v", 0, "Specific version to retrieve") - - getMetadataCmd.AddCommand(getCmd) - return getMetadataCmd -} - -// printSecretResponse prints secret metadata -func printSecretResponse(response *reqres.SecretMetadataResponse) { - printSeparator := func() { - fmt.Println(strings.Repeat("-", 50)) - } - - formatTime := func(t time.Time) string { - return t.Format("2006-01-02 15:04:05 MST") - } - - if response.Metadata != (reqres.SecretRawMetadataResponse{}) { - fmt.Println("\nMetadata:") - printSeparator() - fmt.Printf("Current Version : %d\n", response.Metadata.CurrentVersion) - fmt.Printf("Oldest Version : %d\n", response.Metadata.OldestVersion) - fmt.Printf("Created Time : %s\n", formatTime(response.Metadata.CreatedTime)) - fmt.Printf("Last Updated : %s\n", formatTime(response.Metadata.UpdatedTime)) - fmt.Printf("Max Versions : %d\n", response.Metadata.MaxVersions) - printSeparator() - } - - if len(response.Versions) > 0 { - fmt.Println("\nSecret Versions:") - printSeparator() - - for version, versionData := range response.Versions { - fmt.Printf("Version %d:\n", version) - fmt.Printf(" Created: %s\n", formatTime(versionData.CreatedTime)) - if versionData.DeletedTime != nil { - fmt.Printf(" Deleted: %s\n", formatTime(*versionData.DeletedTime)) - } - printSeparator() - } - } - - if response.Err != "" { - fmt.Printf("\nError: %s\n", response.Err) - printSeparator() - } -} diff --git a/app/spike/internal/cmd/secret/list.go b/app/spike/internal/cmd/secret/list.go index 13d4e03..68ce2da 100644 --- a/app/spike/internal/cmd/secret/list.go +++ b/app/spike/internal/cmd/secret/list.go @@ -6,6 +6,7 @@ package secret import ( "fmt" + "github.com/spf13/cobra" "github.com/spiffe/go-spiffe/v2/workloadapi" spike "github.com/spiffe/spike-sdk-go/api" @@ -58,13 +59,17 @@ func newSecretListCommand(source *workloadapi.X509Source) *cobra.Command { fmt.Println("Error listing secret keys:", err) return } + if keys == nil { + fmt.Println("No secrets found") + return + } - if len(keys) == 0 { + if len(*keys) == 0 { fmt.Println("No secrets found") return } - for _, key := range keys { + for _, key := range *keys { fmt.Printf("- %s\n", key) } }, diff --git a/app/spike/internal/cmd/secret/metadata_get.go b/app/spike/internal/cmd/secret/metadata_get.go new file mode 100644 index 0000000..80f5abf --- /dev/null +++ b/app/spike/internal/cmd/secret/metadata_get.go @@ -0,0 +1,92 @@ +// \\ SPIKE: Secure your secrets with SPIFFE. +// \\\\\ Copyright 2024-present SPIKE contributors. +// \\\\\\\ SPDX-License-Identifier: Apache-2.0 + +package secret + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spiffe/go-spiffe/v2/workloadapi" + spike "github.com/spiffe/spike-sdk-go/api" + "github.com/spiffe/spike-sdk-go/api/entity/data" +) + +// newSecretMetadataGetCommand creates and returns a new cobra.Command for +// retrieving secrets. It configures a command that fetches and displays secret +// data from a specified path. +// +// Parameters: +// - source: X.509 source for workload API authentication +// +// The command accepts a single argument: +// - path: Location of the secret to retrieve +// +// Flags: +// - --version, -v (int): Specific version of the secret to retrieve +// (default 0) where 0 represents the current version +// +// Returns: +// - *cobra.Command: Configured get command +// +// The command will: +// 1. Verify SPIKE initialization status via admin token +// 2. Retrieve the secret metadata from the specified path and version +// 3. Display all metadata fields and secret versions +// +// Error cases: +// - SPIKE not initialized: Prompts user to run 'spike init' +// - Secret not found: Displays appropriate message +// - Read errors: Displays error message +func newSecretMetadataGetCommand( + source *workloadapi.X509Source, +) *cobra.Command { + cmd := &cobra.Command{ + Use: "metadata", + Short: "Manage secret metadata", + } + + var getCmd = &cobra.Command{ + Use: "get ", + Short: "Gets secret metadata from the specified path", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + state, err := spike.CheckInitState(source) + if err != nil { + fmt.Println("Failed to check initialization state:") + fmt.Println(err.Error()) + return + } + + if state == data.NotInitialized { + fmt.Println( + "Please initialize SPIKE first by running 'spike init'.", + ) + return + } + + path := args[0] + version, _ := cmd.Flags().GetInt("version") + + secret, err := spike.GetSecretMetadata(source, path, version) + if err != nil { + fmt.Println("Error reading secret:", err.Error()) + return + } + + if secret == nil { + fmt.Println("Secret not found.") + return + } + + printSecretResponse(secret) + }, + } + + getCmd.Flags().IntP("version", "v", 0, "Specific version to retrieve") + + cmd.AddCommand(getCmd) + + return cmd +} diff --git a/app/spike/internal/cmd/secret/print.go b/app/spike/internal/cmd/secret/print.go new file mode 100644 index 0000000..b2e722f --- /dev/null +++ b/app/spike/internal/cmd/secret/print.go @@ -0,0 +1,45 @@ +package secret + +import ( + "fmt" + "github.com/spiffe/spike-sdk-go/api/entity/data" + "strings" + "time" +) + +func formatTime(t time.Time) string { + return t.Format("2006-01-02 15:04:05 MST") +} + +// printSecretResponse prints secret metadata +func printSecretResponse(response *data.SecretMetadata) { + printSeparator := func() { + fmt.Println(strings.Repeat("-", 50)) + } + + hasMetadata := response.Metadata != (data.SecretMetaDataContent{}) + if hasMetadata { + fmt.Println("\nMetadata:") + printSeparator() + fmt.Printf("Current Version : %d\n", response.Metadata.CurrentVersion) + fmt.Printf("Oldest Version : %d\n", response.Metadata.OldestVersion) + fmt.Printf("Created Time : %s\n", formatTime(response.Metadata.CreatedTime)) + fmt.Printf("Last Updated : %s\n", formatTime(response.Metadata.UpdatedTime)) + fmt.Printf("Max Versions : %d\n", response.Metadata.MaxVersions) + printSeparator() + } + + if len(response.Versions) > 0 { + fmt.Println("\nSecret Versions:") + printSeparator() + + for version, versionData := range response.Versions { + fmt.Printf("Version %d:\n", version) + fmt.Printf(" Created: %s\n", formatTime(versionData.CreatedTime)) + if versionData.DeletedTime != nil { + fmt.Printf(" Deleted: %s\n", formatTime(*versionData.DeletedTime)) + } + printSeparator() + } + } +} diff --git a/app/spike/internal/cmd/secret/undelete.go b/app/spike/internal/cmd/secret/undelete.go index feb22ec..3b3b50d 100644 --- a/app/spike/internal/cmd/secret/undelete.go +++ b/app/spike/internal/cmd/secret/undelete.go @@ -98,7 +98,18 @@ Examples: } } - err = spike.UndeleteSecret(source, path, versionList) + var vv []int + for _, v := range versionList { + iv, err := strconv.Atoi(v) + if err == nil { + vv = append(vv, iv) + } + } + if vv == nil { + vv = []int{} + } + + err = spike.UndeleteSecret(source, path, vv) if err != nil { fmt.Printf("Error: %v\n", err) return diff --git a/go.mod b/go.mod index 6bed2de..41d9c29 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.24 github.com/spf13/cobra v1.8.1 github.com/spiffe/go-spiffe/v2 v2.4.0 - github.com/spiffe/spike-sdk-go v0.1.12 + github.com/spiffe/spike-sdk-go v0.1.13 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.26.0 ) diff --git a/go.sum b/go.sum index 691d1d7..349e7d3 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/spiffe/spike-sdk-go v0.1.11 h1:PuPpwyjF1i4HPQHGGOuHVICLKpP09q+WffqyHH github.com/spiffe/spike-sdk-go v0.1.11/go.mod h1:WIserWbShAkDVoj+GYdcXKFmKp2IBIsZHKKDcStHrHw= github.com/spiffe/spike-sdk-go v0.1.12 h1:5fS31yLdIMEtaTk0fpy/8msVFeS/vZTkQaPKyGqaMZ8= github.com/spiffe/spike-sdk-go v0.1.12/go.mod h1:WIserWbShAkDVoj+GYdcXKFmKp2IBIsZHKKDcStHrHw= +github.com/spiffe/spike-sdk-go v0.1.13 h1:vQIguhkmztUSM55/xYxXPed5laoF1Q4sazA1GRg66pw= +github.com/spiffe/spike-sdk-go v0.1.13/go.mod h1:WIserWbShAkDVoj+GYdcXKFmKp2IBIsZHKKDcStHrHw= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= diff --git a/jira.xml b/jira.xml index b39ccf8..954efc9 100644 --- a/jira.xml +++ b/jira.xml @@ -43,6 +43,9 @@ * demo about SDK * demo about metadata api. + + Maybe have `source` as part of the spike constructor. + What's needed for v1.0.0: