diff --git a/Makefile b/Makefile index 5f300e5..f69283d 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,10 @@ protoc-install: oapi-codegen: oapi-codegen -package insightapi -generate types ./api/insights-v1.yml > ./gen/insightapi/insights.types.go oapi-codegen -package insightapi -generate client ./api/insights-v1.yml > ./gen/insightapi/insights.client.go - oapi-codegen -package controlplane -generate types ./api/cp-v1-trials.yml > ./gen/controlplane/trials.types.go - oapi-codegen -package controlplane -generate client ./api/cp-v1-trials.yml > ./gen/controlplane/trials.client.go + oapi-codegen -package cpv1trials -generate types ./api/cp-v1-trials.yml > ./gen/cpv1trials/trials.types.go + oapi-codegen -package cpv1trials -generate client ./api/cp-v1-trials.yml > ./gen/cpv1trials/trials.client.go + oapi-codegen -package cpv1 -generate types ./api/cp-v1.yml > ./gen/cpv1/cp.types.go + oapi-codegen -package cpv1 -generate client ./api/cp-v1.yml > ./gen/cpv1/cp.client.go protoc-codegen: protoc -I ./api \ @@ -33,7 +35,8 @@ protoc-codegen: setup: mkdir -p out \ gen/insightapi \ - gen/controlplane \ + gen/cpv1trials \ + gen/cpv1 \ gen/filterinput \ gen/filtersuite \ gen/exceptionsapi diff --git a/README.md b/README.md index fbb2593..4cb720a 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,16 @@ vet auth configure ``` > Insights API is used to enrich OSS packages with meta-data for rich query and policy -> decisions +> decisions. Alternatively, the API key can be passed through environment +> variable `VET_API_KEY` -Run `vet` to identify risks +Verify authentication token is valid + +```bash +vet auth verify +``` + +Run `vet` to identify risks in a source repository ```bash vet scan -D /path/to/repository diff --git a/api/cp-v1.yml b/api/cp-v1.yml new file mode 100644 index 0000000..ccff190 --- /dev/null +++ b/api/cp-v1.yml @@ -0,0 +1,113 @@ +openapi: 3.0.2 +info: + title: SafeDep Control Plane API + contact: + name: SafeDep API + url: 'https://safedep.io' + description: | + The SafeDep Control Plane API provides configuration and management plane + access to clients for the SafeDep platform + version: 0.0.1 +servers: + - url: 'https://{apiHost}/{apiBase}' + variables: + apiHost: + default: api.safedep.io + apiBase: + default: control-plane/v1 +tags: + - name: Control Plane + description: Control Plane API +paths: + /auths/me: + get: + description: | + Introspection API for getting configuration information associated + with the supplied API credentials + operationId: getApiCredentialIntrospection + tags: + - Control Plane + responses: + '200': + description: API credentials introspection information + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialIntrospectionResponse' + '403': + description: Access to the API is denied + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '429': + description: Rate limit block + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '500': + description: Failed due to internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' +components: + securitySchemes: + api_key: + type: apiKey + name: Authorization + in: header + schemas: + ApiError: + type: object + properties: + message: + type: string + description: A descriptive message about the error meant for developer consumption + type: + type: string + description: An optional service or domain specific error group + enum: + - invalid_request + - operation_failed + - internal_error + code: + type: string + description: An error code identifying the error + enum: + - api_guard_invalid_credentials + - api_guard_rate_limit_exceeded + - api_guard_unauthorized + - api_guard_error + - app_generic_error + - app_security_error + - app_insufficient_parameters + - app_feature_not_enabled + - app_package_version_not_found + params: + type: object + description: Optional error specific attributes + additionalProperties: + type: object + properties: + key: + type: string + value: + type: string + CredentialIntrospectionResponse: + type: object + properties: + type: + type: string + description: The entity type to which this credential belongs to + enum: + - TrialUser + - Organization + - Team + - User + expiry: + type: string + description: Expiry timestamp in RFC3399 format if expirable + required: + - type diff --git a/auth.go b/auth.go index 143dbc1..1bdd769 100644 --- a/auth.go +++ b/auth.go @@ -10,6 +10,7 @@ import ( "golang.org/x/term" "github.com/safedep/vet/internal/auth" + "github.com/safedep/vet/internal/ui" ) var ( @@ -27,6 +28,9 @@ func newAuthCommand() *cobra.Command { }, } + cmd.PersistentFlags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "", + auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API") + cmd.AddCommand(configureAuthCommand()) cmd.AddCommand(verifyAuthCommand()) cmd.AddCommand(trialsRegisterCommand()) @@ -45,8 +49,9 @@ func configureAuthCommand() *cobra.Command { } err = auth.Configure(auth.Config{ - ApiUrl: authInsightApiBaseUrl, - ApiKey: string(key), + ApiUrl: authInsightApiBaseUrl, + ApiKey: string(key), + ControlPlaneApiUrl: authControlPlaneApiBaseUrl, }) if err != nil { @@ -69,8 +74,11 @@ func verifyAuthCommand() *cobra.Command { cmd := &cobra.Command{ Use: "verify", RunE: func(cmd *cobra.Command, args []string) error { - fmt.Printf("Verify auth command is currently work in progress\n") - os.Exit(1) + failOnError("auth/verify", auth.Verify(&auth.VerifyConfig{ + ControlPlaneApiUrl: authControlPlaneApiBaseUrl, + })) + + ui.PrintSuccess("Authentication key is valid!") return nil }, } @@ -103,8 +111,6 @@ func trialsRegisterCommand() *cobra.Command { cmd.Flags().StringVarP(&authTrialEmail, "email", "", "", "Email address to use for sending trial API key") - cmd.Flags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "", - auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API for registrations") return cmd } diff --git a/gen/cpv1/cp.client.go b/gen/cpv1/cp.client.go new file mode 100644 index 0000000..6e9d746 --- /dev/null +++ b/gen/cpv1/cp.client.go @@ -0,0 +1,258 @@ +// Package cpv1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT. +package cpv1 + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetApiCredentialIntrospection request + GetApiCredentialIntrospection(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetApiCredentialIntrospection(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetApiCredentialIntrospectionRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetApiCredentialIntrospectionRequest generates requests for GetApiCredentialIntrospection +func NewGetApiCredentialIntrospectionRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auths/me") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetApiCredentialIntrospection request + GetApiCredentialIntrospectionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiCredentialIntrospectionResponse, error) +} + +type GetApiCredentialIntrospectionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CredentialIntrospectionResponse + JSON403 *ApiError + JSON429 *ApiError + JSON500 *ApiError +} + +// Status returns HTTPResponse.Status +func (r GetApiCredentialIntrospectionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetApiCredentialIntrospectionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetApiCredentialIntrospectionWithResponse request returning *GetApiCredentialIntrospectionResponse +func (c *ClientWithResponses) GetApiCredentialIntrospectionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiCredentialIntrospectionResponse, error) { + rsp, err := c.GetApiCredentialIntrospection(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetApiCredentialIntrospectionResponse(rsp) +} + +// ParseGetApiCredentialIntrospectionResponse parses an HTTP response from a GetApiCredentialIntrospectionWithResponse call +func ParseGetApiCredentialIntrospectionResponse(rsp *http.Response) (*GetApiCredentialIntrospectionResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetApiCredentialIntrospectionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CredentialIntrospectionResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ApiError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest ApiError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest ApiError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} diff --git a/gen/cpv1/cp.types.go b/gen/cpv1/cp.types.go new file mode 100644 index 0000000..b35da22 --- /dev/null +++ b/gen/cpv1/cp.types.go @@ -0,0 +1,159 @@ +// Package cpv1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT. +package cpv1 + +import ( + "encoding/json" + "fmt" +) + +// Defines values for ApiErrorCode. +const ( + ApiErrorCodeApiGuardError ApiErrorCode = "api_guard_error" + + ApiErrorCodeApiGuardInvalidCredentials ApiErrorCode = "api_guard_invalid_credentials" + + ApiErrorCodeApiGuardRateLimitExceeded ApiErrorCode = "api_guard_rate_limit_exceeded" + + ApiErrorCodeApiGuardUnauthorized ApiErrorCode = "api_guard_unauthorized" + + ApiErrorCodeAppFeatureNotEnabled ApiErrorCode = "app_feature_not_enabled" + + ApiErrorCodeAppGenericError ApiErrorCode = "app_generic_error" + + ApiErrorCodeAppInsufficientParameters ApiErrorCode = "app_insufficient_parameters" + + ApiErrorCodeAppPackageVersionNotFound ApiErrorCode = "app_package_version_not_found" + + ApiErrorCodeAppSecurityError ApiErrorCode = "app_security_error" +) + +// Defines values for ApiErrorType. +const ( + ApiErrorTypeInternalError ApiErrorType = "internal_error" + + ApiErrorTypeInvalidRequest ApiErrorType = "invalid_request" + + ApiErrorTypeOperationFailed ApiErrorType = "operation_failed" +) + +// Defines values for CredentialIntrospectionResponseType. +const ( + CredentialIntrospectionResponseTypeOrganization CredentialIntrospectionResponseType = "Organization" + + CredentialIntrospectionResponseTypeTeam CredentialIntrospectionResponseType = "Team" + + CredentialIntrospectionResponseTypeTrialUser CredentialIntrospectionResponseType = "TrialUser" + + CredentialIntrospectionResponseTypeUser CredentialIntrospectionResponseType = "User" +) + +// ApiError defines model for ApiError. +type ApiError struct { + // An error code identifying the error + Code *ApiErrorCode `json:"code,omitempty"` + + // A descriptive message about the error meant for developer consumption + Message *string `json:"message,omitempty"` + + // Optional error specific attributes + Params *ApiError_Params `json:"params,omitempty"` + + // An optional service or domain specific error group + Type *ApiErrorType `json:"type,omitempty"` +} + +// An error code identifying the error +type ApiErrorCode string + +// Optional error specific attributes +type ApiError_Params struct { + AdditionalProperties map[string]struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` + } `json:"-"` +} + +// An optional service or domain specific error group +type ApiErrorType string + +// CredentialIntrospectionResponse defines model for CredentialIntrospectionResponse. +type CredentialIntrospectionResponse struct { + // Expiry timestamp in RFC3399 format if expirable + Expiry *string `json:"expiry,omitempty"` + + // The entity type to which this credential belongs to + Type CredentialIntrospectionResponseType `json:"type"` +} + +// The entity type to which this credential belongs to +type CredentialIntrospectionResponseType string + +// Getter for additional properties for ApiError_Params. Returns the specified +// element and whether it was found +func (a ApiError_Params) Get(fieldName string) (value struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for ApiError_Params +func (a *ApiError_Params) Set(fieldName string, value struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` + }) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for ApiError_Params to handle AdditionalProperties +func (a *ApiError_Params) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` + }) + for fieldName, fieldBuf := range object { + var fieldVal struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` + } + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for ApiError_Params to handle AdditionalProperties +func (a ApiError_Params) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} diff --git a/gen/controlplane/trials.client.go b/gen/cpv1trials/trials.client.go similarity index 98% rename from gen/controlplane/trials.client.go rename to gen/cpv1trials/trials.client.go index 2d20d37..71357ee 100644 --- a/gen/controlplane/trials.client.go +++ b/gen/cpv1trials/trials.client.go @@ -1,7 +1,7 @@ -// Package controlplane provides primitives to interact with the openapi HTTP API. +// Package cpv1trials provides primitives to interact with the openapi HTTP API. // // Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT. -package controlplane +package cpv1trials import ( "bytes" diff --git a/gen/controlplane/trials.types.go b/gen/cpv1trials/trials.types.go similarity index 97% rename from gen/controlplane/trials.types.go rename to gen/cpv1trials/trials.types.go index b1b9c8a..f98dab8 100644 --- a/gen/controlplane/trials.types.go +++ b/gen/cpv1trials/trials.types.go @@ -1,7 +1,7 @@ -// Package controlplane provides primitives to interact with the openapi HTTP API. +// Package cpv1trials provides primitives to interact with the openapi HTTP API. // // Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT. -package controlplane +package cpv1trials import ( "encoding/json" diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 4b4de2c..0010abe 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -10,8 +10,9 @@ import ( ) const ( - apiUrlEnvKey = "VET_INSIGHTS_API_URL" - apiKeyEnvKey = "VET_INSIGHTS_API_KEY" + apiUrlEnvKey = "VET_INSIGHTS_API_URL" + apiKeyEnvKey = "VET_INSIGHTS_API_KEY" + apiKeyAlternateEnvKey = "VET_API_KEY" defaultApiUrl = "https://api.safedep.io/insights/v1" defaultControlPlaneApiUrl = "https://api.safedep.io/control-plane/v1" @@ -20,8 +21,9 @@ const ( ) type Config struct { - ApiUrl string `yaml:"api_url"` - ApiKey string `yaml:"api_key"` + ApiUrl string `yaml:"api_url"` + ApiKey string `yaml:"api_key"` + ControlPlaneApiUrl string `yaml:"cp_api_url"` } // Global config to be used during runtime @@ -36,16 +38,15 @@ func Configure(m Config) error { return persistConfiguration() } -func Verify() error { - // TODO: Verify by actually calling insight API - return nil -} - func DefaultApiUrl() string { return defaultApiUrl } func DefaultControlPlaneApiUrl() string { + if (globalConfig != nil) && (globalConfig.ControlPlaneApiUrl != "") { + return globalConfig.ControlPlaneApiUrl + } + return defaultControlPlaneApiUrl } @@ -66,6 +67,10 @@ func ApiKey() string { return key } + if key, ok := os.LookupEnv(apiKeyAlternateEnvKey); ok { + return key + } + if globalConfig != nil { return globalConfig.ApiKey } diff --git a/internal/auth/trial.go b/internal/auth/trial.go index 9b71f66..11d14a4 100644 --- a/internal/auth/trial.go +++ b/internal/auth/trial.go @@ -9,7 +9,7 @@ import ( "github.com/deepmap/oapi-codegen/pkg/types" "github.com/safedep/dry/utils" - "github.com/safedep/vet/gen/controlplane" + "github.com/safedep/vet/gen/cpv1trials" "github.com/safedep/vet/pkg/common/logger" apierr "github.com/safedep/dry/errors" @@ -45,7 +45,7 @@ func (client *trialRegistrationClient) Execute() (*trialRegistrationResponse, er logger.Infof("Trial registrations using Control Plane: %s", client.config.ControlPlaneApiUrl) - cpClient, err := controlplane.NewClientWithResponses(client.config.ControlPlaneApiUrl) + cpClient, err := cpv1trials.NewClientWithResponses(client.config.ControlPlaneApiUrl) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func (client *trialRegistrationClient) Execute() (*trialRegistrationResponse, er client.config.Email) res, err := cpClient.RegisterTrialUserWithResponse(context.Background(), - controlplane.RegisterTrialUserJSONRequestBody{ + cpv1trials.RegisterTrialUserJSONRequestBody{ Email: types.Email(client.config.Email), }) if err != nil { diff --git a/internal/auth/verify.go b/internal/auth/verify.go new file mode 100644 index 0000000..fe38d52 --- /dev/null +++ b/internal/auth/verify.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + "fmt" + "net/http" + + apierr "github.com/safedep/dry/errors" + "github.com/safedep/dry/utils" + "github.com/safedep/vet/gen/cpv1" + "github.com/safedep/vet/pkg/common/logger" +) + +type VerifyConfig struct { + ControlPlaneApiUrl string +} + +// Verify function takes config and current API key available +// from this package and returns an error if auth is invalid +func Verify(config *VerifyConfig) error { + logger.Infof("Verifying auth token using Control Plane: %s", config.ControlPlaneApiUrl) + + client, err := cpv1.NewClientWithResponses(config.ControlPlaneApiUrl) + if err != nil { + return err + } + + authKeyApplier := func(ctx context.Context, req *http.Request) error { + req.Header.Set("Authorization", ApiKey()) + return nil + } + + resp, err := client.GetApiCredentialIntrospectionWithResponse(context.Background(), + authKeyApplier) + if err != nil { + return err + } + + if resp.StatusCode() != http.StatusOK { + if err, ok := apierr.UnmarshalApiError(resp.Body); ok { + return err + } else { + return fmt.Errorf("unexpected status code:%d from control plane", + resp.HTTPResponse.StatusCode) + } + + } + + if resp.JSON200 == nil { + return fmt.Errorf("invalid nil response from server") + } + + logger.Infof("Current auth token is valid with expiry: %s", + utils.SafelyGetValue(resp.JSON200.Expiry)) + + return nil +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go index ef293b9..16ba3bc 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -11,6 +11,11 @@ func PrintBanner(s string) { fmt.Fprintf(os.Stderr, s) } +func PrintSuccess(s string, args ...any) { + msg := fmt.Sprintf(s, args...) + fmt.Fprint(os.Stderr, text.FgGreen.Sprint(msg), "\n") +} + func PrintError(s string, args ...any) { msg := fmt.Sprintf(s, args...) fmt.Fprint(os.Stderr, text.FgRed.Sprint(msg), "\n") diff --git a/scan.go b/scan.go index 9d9d5ae..8868f49 100644 --- a/scan.go +++ b/scan.go @@ -5,6 +5,7 @@ import ( "os" "github.com/safedep/dry/utils" + "github.com/safedep/vet/internal/auth" "github.com/safedep/vet/internal/ui" "github.com/safedep/vet/pkg/analyzer" "github.com/safedep/vet/pkg/models" @@ -15,20 +16,21 @@ import ( ) var ( - lockfiles []string - lockfileAs string - baseDirectory string - transitiveAnalysis bool - transitiveDepth int - concurrency int - dumpJsonManifestDir string - celFilterExpression string - celFilterSuiteFile string - celFilterFailOnMatch bool - markdownReportPath string - consoleReport bool - summaryReport bool - silentScan bool + lockfiles []string + lockfileAs string + baseDirectory string + transitiveAnalysis bool + transitiveDepth int + concurrency int + dumpJsonManifestDir string + celFilterExpression string + celFilterSuiteFile string + celFilterFailOnMatch bool + markdownReportPath string + consoleReport bool + summaryReport bool + silentScan bool + disableAuthVerifyBeforeScan bool ) func newScanCommand() *cobra.Command { @@ -68,6 +70,8 @@ func newScanCommand() *cobra.Command { "Filter packages using CEL Filter Suite from file") cmd.Flags().BoolVarP(&celFilterFailOnMatch, "filter-fail", "", false, "Fail the scan if the filter match any package (security gate)") + cmd.Flags().BoolVarP(&disableAuthVerifyBeforeScan, "no-verify-auth", "", false, + "Do not verify auth token before starting scan") cmd.Flags().StringVarP(&markdownReportPath, "report-markdown", "", "", "Generate consolidated markdown report to file") cmd.Flags().BoolVarP(&consoleReport, "report-console", "", false, @@ -97,6 +101,12 @@ func listParsersCommand() *cobra.Command { } func startScan() { + if !disableAuthVerifyBeforeScan { + failOnError("auth/verify", auth.Verify(&auth.VerifyConfig{ + ControlPlaneApiUrl: auth.DefaultControlPlaneApiUrl(), + })) + } + failOnError("scan", internalStartScan()) }