Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Commands:
## Usage: DAST Scans

```
Usage: nullify dast [--app-name APP-NAME] [--spec-path SPEC-PATH] [--target-host TARGET-HOST] [--header HEADER] [--github-owner GITHUB-OWNER] [--github-repo GITHUB-REPO] [--local] [--image-label IMAGE-LABEL] [--force-pull] [--use-host-network]
Usage: nullify dast [--app-name APP-NAME] [--spec-path SPEC-PATH] [--target-host TARGET-HOST] [--header HEADER] [--auth-config AUTH-CONFIG] [--github-owner GITHUB-OWNER] [--github-repo GITHUB-REPO] [--local] [--image-label IMAGE-LABEL] [--force-pull] [--use-host-network]

Options:
--app-name APP-NAME The unique name of the app to be scanned, you can set this to anything e.g. Core API
Expand All @@ -65,6 +65,8 @@ Options:
--target-host TARGET-HOST
The base URL of the API to be scanned e.g. https://api.nullify.ai
--header HEADER List of headers for the DAST agent to authenticate with your API, separated by commas e.g. "Authorization: Bearer 1234,X-Custom-Header: abcxyz"
--auth-config AUTH-CONFIG
The path to the auth config file
--github-owner GITHUB-OWNER
The GitHub username or organisation
--github-repo GITHUB-REPO
Expand Down Expand Up @@ -160,4 +162,5 @@ The locally hosted scan can be run from within private networks to test private
| **`header`** | List of headers for the DAST agent to authenticate with your API, string seperated by commas | `false` | |
| **`local`** | Test the given app locally for bugs and vulnerabilities in private networks | `false` | |
| **`version`** | Version of the DAST local image that is used for scanning [default: ] | `false` | latest |
| **`auth-config`** | The path to the auth config file | `false` | |

10 changes: 5 additions & 5 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
)

type args struct {
DAST *dast.DAST `arg:"subcommand:dast" help:"Test the given app for bugs and vulnerabilities"`
Host string `arg:"--host" default:"api.nullify.ai" help:"The base URL of your Nullify API instance"`
Verbose bool `arg:"-v" help:"Enable verbose logging"`
Debug bool `arg:"-d" help:"Enable debug logging"`

DAST *dast.DAST `arg:"subcommand:dast" help:"Test the given app for bugs and vulnerabilities"`
Host string `arg:"--host" default:"api.nullify.ai" help:"The base URL of your Nullify API instance"`
Verbose bool `arg:"-v" help:"Enable verbose logging"`
Debug bool `arg:"-d" help:"Enable debug logging"`
AuthConfig string `arg:"--auth-config" help:"The path to the auth config file"`
models.AuthSources
}

Expand Down
25 changes: 19 additions & 6 deletions internal/dast/dast.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type DAST struct {
ImageLabel string `arg:"--image-label" default:"latest" help:"Version of the DAST local image that is used for scanning"`
ForcePullImage bool `arg:"--force-pull" help:"Force a docker pull of the latest version of the DAST local image"`
UseHostNetwork bool `arg:"--use-host-network" help:"Use the host network for the DAST local scan"`

AuthConfig string `arg:"--auth-config" help:"The path to the auth config file"`
}

func RunDASTScan(ctx context.Context, dast *DAST, nullifyClient *client.NullifyClient, logLevel string) error {
Expand All @@ -38,6 +40,21 @@ func RunDASTScan(ctx context.Context, dast *DAST, nullifyClient *client.NullifyC
return err
}

// Create auth config
authConfig := models.AuthConfig{
Headers: authHeaders,
}

// Read auth config file
if dast.AuthConfig != "" {
fileAuthConfig, err := lib.ParseAuthConfig(ctx, dast.AuthConfig)
if err != nil {
logger.L(ctx).Error("failed to parse auth config", logger.Err(err))
return err
}
authConfig = *fileAuthConfig
}

if dast.Local {
logger.L(ctx).Info("starting local scan")
err = RunLocalScan(
Expand All @@ -49,9 +66,7 @@ func RunDASTScan(ctx context.Context, dast *DAST, nullifyClient *client.NullifyC
AppName: dast.AppName,
TargetHost: dast.TargetHost,
OpenAPISpec: spec,
AuthConfig: models.AuthConfig{
Headers: authHeaders,
},
AuthConfig: authConfig,
},
dast.ImageLabel,
dast.ForcePullImage,
Expand All @@ -69,9 +84,7 @@ func RunDASTScan(ctx context.Context, dast *DAST, nullifyClient *client.NullifyC
Host: dast.TargetHost,
TargetHost: dast.TargetHost,
OpenAPISpec: spec,
AuthConfig: models.AuthConfig{
Headers: authHeaders,
},
AuthConfig: authConfig,
RequestProvider: models.RequestProvider{
GitHubOwner: dast.GitHubOwner,
},
Expand Down
32 changes: 32 additions & 0 deletions internal/lib/auth_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package lib

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/nullify-platform/cli/internal/models"
"github.com/nullify-platform/logger/pkg/logger"
)

// ParseAuthConfig reads and parses an authentication configuration file
func ParseAuthConfig(ctx context.Context, configPath string) (*models.AuthConfig, error) {
if configPath == "" {
return &models.AuthConfig{}, nil
}

logger.L(ctx).Debug("parsing auth config file", logger.String("path", configPath))

data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read auth config file: %w", err)
}

var authConfig models.AuthConfig
if err := json.Unmarshal(data, &authConfig); err != nil {
return nil, fmt.Errorf("failed to parse auth config file: %w", err)
}

return &authConfig, nil
}
58 changes: 57 additions & 1 deletion internal/models/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,62 @@ type AuthSources struct {
GitHubToken string `json:"githubToken" arg:"--github-token" help:"GitHub actions job token to exchange for a Nullify API token"`
}

type AuthMethod string

const (
AuthMethodNone AuthMethod = "none"
AuthMethodBasic AuthMethod = "basic"
AuthMethodBearer AuthMethod = "bearer"
AuthMethodSession AuthMethod = "session"
AuthMethodOAuth AuthMethod = "oauth"
AuthMethodSAML AuthMethod = "saml"
AuthMethodJWT AuthMethod = "jwt"
AuthMethodCustom AuthMethod = "custom"
)

// AuthConfig represents the authentication configuration for Nullify DAST
type AuthConfig struct {
Headers map[string]string `json:"headers"`
// Single user authentication fields
Method AuthMethod `json:"method,omitempty"`
Username string `json:"username,omitempty"`
UserDescription string `json:"userDescription,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
Scope string `json:"scope,omitempty"`
LoginURL string `json:"loginUrl,omitempty"`
LoginBody interface{} `json:"loginBody,omitempty"`
LoginSelector string `json:"loginSelector,omitempty"`
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
CustomParams map[string]interface{} `json:"customParams,omitempty"`

// Multi-user authentication fields
AuthorizationModel bool `json:"authorizationModel,omitempty"`
Users []UserAuth `json:"users,omitempty"`
}

type UserAuth struct {
RoleName string `json:"roleName"`
RoleDescription string `json:"roleDescription,omitempty"`
UserDescription string `json:"userDescription,omitempty"`
AuthConfig MultiUserAuthConfig `json:"authConfig"`
}

type MultiUserAuthConfig struct {
Method AuthMethod `json:"method,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
Scope string `json:"scope,omitempty"`
LoginURL string `json:"loginUrl,omitempty"`
LoginBody interface{} `json:"loginBody,omitempty"`
LoginSelector string `json:"loginSelector,omitempty"`
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
CustomParams map[string]interface{} `json:"customParams,omitempty"`
}
Loading