Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: snyk/cli
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.917.0
Choose a base ref
...
head repository: snyk/cli
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.918.0
Choose a head ref
  • 10 commits
  • 26 files changed
  • 8 contributors

Commits on May 3, 2022

  1. chore: fix broken proxy test in cli v2

    Avishagp committed May 3, 2022

    Unverified

    No user is associated with the committer email.
    Copy the full SHA
    c82fcc0 View commit details
  2. Merge pull request #3196 from snyk/chore/v2-fix-broken-proxy-test

    chore: fix broken proxy test in cli v2
    Avishagp authored May 3, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    61b826d View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    maxjeffos Jeff McLean
    Copy the full SHA
    76886c5 View commit details
  4. Merge pull request #3193 from snyk/chore/set-v2-version-in-x-snyk-cli…

    …-version
    
    Replace value of x-snyk-cli-version with v2 version if set
    maxjeffos authored May 3, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c9ac9c2 View commit details

Commits on May 4, 2022

  1. Unverified

    The email in this signature doesn’t match the committer email.
    Copy the full SHA
    7651dc2 View commit details
  2. fix: cliv2 unsupported combination version + —json-file-output

    * re-enable disabled test for cliv2
    
    Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>
    
    fix: run formatter to avoid linter error
    
    Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>
    PeterSchafer committed May 4, 2022

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    Copy the full SHA
    493dba5 View commit details
  3. Merge pull request #3199 from snyk/iac_drift_enable_deep_for_all

    iac describe: `--all` flag should show unmanaged, managed and drift
    eliecharra authored May 4, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    69fd7b3 View commit details
  4. Merge pull request #3200 from snyk/fix/cliv2_version_parameter

    fix: cliv2 unsupported combination version + —json-file-output
    PeterSchafer authored May 4, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a7bef35 View commit details
  5. Verified

    This commit was signed with the committer’s verified signature.
    ofekatr Ofek A
    Copy the full SHA
    c339455 View commit details
  6. Merge pull request #3145 from snyk/chore/iac-output-issue-description-c…

    …fg-1574
    
    feat: Implemented new issue description [CFG-1574]
    ofekatr authored May 4, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f72da98 View commit details
Showing with 1,381 additions and 1,071 deletions.
  1. +2 −1 cliv2/cmd/cliv2/main.go
  2. +14 −1 cliv2/cmd/make-cert/main.go
  3. +6 −1 cliv2/internal/certs/certs.go
  4. +11 −1 cliv2/internal/cliv2/cliv2.go
  5. +11 −4 cliv2/internal/proxy/proxy.go
  6. +63 −2 cliv2/internal/proxy/proxy_test.go
  7. +2 −2 src/cli/commands/test/iac/index.ts
  8. +1 −0 src/cli/commands/test/iac/local-execution/process-results/results-formatter.ts
  9. +0 −28 src/lib/formatters/iac-output/v2/formatters.ts
  10. +0 −58 src/lib/formatters/iac-output/v2/issues-list.ts
  11. +29 −0 src/lib/formatters/iac-output/v2/issues-list/formatters.ts
  12. +52 −0 src/lib/formatters/iac-output/v2/issues-list/index.ts
  13. +59 −0 src/lib/formatters/iac-output/v2/issues-list/issue.ts
  14. +19 −0 src/lib/formatters/iac-output/v2/issues-list/types.ts
  15. +0 −19 src/lib/formatters/iac-output/v2/types.ts
  16. +7 −0 src/lib/iac/constants.ts
  17. +1 −1 src/lib/iac/drift/driftctl.ts
  18. +4 −0 src/lib/snyk-test/iac-test-result.ts
  19. +0 −8 test/jest/acceptance/cli-args.spec.ts
  20. +3 −3 test/jest/acceptance/iac/describe.spec.ts
  21. +16 −2 test/jest/acceptance/iac/iac-output.spec.ts
  22. +843 −843 test/jest/unit/iac/process-results/fixtures/new-output-formatted-results.json
  23. +0 −93 test/jest/unit/lib/formatters/iac-output/v2/issues-list.spec.ts
  24. +6 −4 test/jest/unit/lib/formatters/iac-output/v2/{ → issues-list}/formatters.spec.ts
  25. +194 −0 test/jest/unit/lib/formatters/iac-output/v2/issues-list/index.spec.ts
  26. +38 −0 test/jest/unit/lib/iac/drift.spec.ts
3 changes: 2 additions & 1 deletion cliv2/cmd/cliv2/main.go
Original file line number Diff line number Diff line change
@@ -70,7 +70,8 @@ func MainWithErrorCode(envVariables EnvironmentVariables, args []string) int {
}

// init proxy object
wrapperProxy, err := proxy.NewWrapperProxy(envVariables.UpstreamProxy, envVariables.CacheDirectory, debugLogger)

wrapperProxy, err := proxy.NewWrapperProxy(envVariables.UpstreamProxy, envVariables.CacheDirectory, cli.GetFullVersion(), debugLogger)
if err != nil {
fmt.Println("Failed to create proxy")
fmt.Println(err)
15 changes: 14 additions & 1 deletion cliv2/cmd/make-cert/main.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package main

import (
"fmt"
"log"
"os"
"path"
"snyk/cling/internal/certs"
"snyk/cling/internal/utils"
"strings"
)

func main() {
certName := os.Args[1]

debugLogger := log.Default()

snykDNSNamesStr := os.Getenv("SNYK_DNS_NAMES")
var snykDNSNames []string
fmt.Println("SNYK_DNS_NAMES:", snykDNSNamesStr)
if snykDNSNamesStr != "" {
snykDNSNames = strings.Split(snykDNSNamesStr, ",")
} else {
// We use app.dev.snyk.io for development
snykDNSNames = []string{"snyk.io", "*.snyk.io", "*.dev.snyk.io"}
}

debugLogger.Println("certificate name:", certName)
debugLogger.Println("SNYK_DNS_NAMES:", snykDNSNames)

certPEMBlockBytes, keyPEMBlockBytes, err := certs.MakeSelfSignedCert(certName, debugLogger)
certPEMBlockBytes, keyPEMBlockBytes, err := certs.MakeSelfSignedCert(certName, snykDNSNames, debugLogger)
if err != nil {
log.Fatal(err)
}
7 changes: 6 additions & 1 deletion cliv2/internal/certs/certs.go
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import (
"time"
)

func MakeSelfSignedCert(certName string, debugLogger *log.Logger) (certPEMBlock []byte, keyPEMBlock []byte, err error) {
func MakeSelfSignedCert(certName string, dnsNames []string, debugLogger *log.Logger) (certPEMBlock []byte, keyPEMBlock []byte, err error) {
// create a key
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
@@ -39,6 +39,11 @@ func MakeSelfSignedCert(certName string, debugLogger *log.Logger) (certPEMBlock
IsCA: true,
}

for _, dnsName := range dnsNames {
template.DNSNames = append(template.DNSNames, dnsName)
debugLogger.Println("MakeSelfSignedCert added ", dnsName)
}

certDERBytes, err_CreateCertificate := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err_CreateCertificate != nil {
return nil, nil, err
12 changes: 11 additions & 1 deletion cliv2/internal/cliv2/cliv2.go
Original file line number Diff line number Diff line change
@@ -114,6 +114,16 @@ func (c *CLI) printVersion() {
fmt.Println(c.GetFullVersion())
}

func (c *CLI) commandVersion(passthroughArgs []string) int {
if utils.Contains(passthroughArgs, "--json-file-output") {
fmt.Println("The following option combination is not currently supported: version + json-file-output")
return SNYK_EXIT_CODE_ERROR
} else {
c.printVersion()
return SNYK_EXIT_CODE_OK
}
}

func determineHandler(passthroughArgs []string) Handler {
result := V1_DEFAULT

@@ -205,7 +215,7 @@ func (c *CLI) Execute(wrapperProxyPort int, fullPathToCert string, passthroughAr

switch {
case handler == V2_VERSION:
c.printVersion()
returnCode = c.commandVersion(passthroughArgs)
default:
returnCode = c.executeV1Default(wrapperProxyPort, fullPathToCert, passthroughArgs)
}
15 changes: 11 additions & 4 deletions cliv2/internal/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -5,15 +5,14 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/elazarl/goproxy"
"log"
"net"
"net/http"
"net/url"
"os"
"snyk/cling/internal/certs"
"snyk/cling/internal/utils"

"github.com/elazarl/goproxy"
)

type WrapperProxy struct {
@@ -23,12 +22,12 @@ type WrapperProxy struct {
CertificateLocation string
}

func NewWrapperProxy(upstreamProxy string, cacheDirectory string, debugLogger *log.Logger) (*WrapperProxy, error) {
func NewWrapperProxy(upstreamProxy string, cacheDirectory string, cliVersion string, debugLogger *log.Logger) (*WrapperProxy, error) {
var p WrapperProxy
p.DebugLogger = debugLogger

certName := "snyk-embedded-proxy"
certPEMBlock, keyPEMBlock, err := certs.MakeSelfSignedCert(certName, p.DebugLogger)
certPEMBlock, keyPEMBlock, err := certs.MakeSelfSignedCert(certName, []string{}, p.DebugLogger)
if err != nil {
return nil, err
}
@@ -63,6 +62,14 @@ func NewWrapperProxy(upstreamProxy string, cacheDirectory string, debugLogger *l
proxy := goproxy.NewProxyHttpServer()
proxy.Logger = debugLogger
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
existingValue := r.Header.Get("x-snyk-cli-version")
if existingValue != "" {
debugLogger.Printf("Replacing value of existing x-snyk-cli-version header (%s) with %s\n", existingValue, cliVersion)
r.Header.Set("x-snyk-cli-version", cliVersion)
}
return r, nil
})

// Set the upstream proxy, if any. For example, if using with a corporate proxy.
if upstreamProxy != "" {
65 changes: 63 additions & 2 deletions cliv2/internal/proxy/proxy_test.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"snyk/cling/internal/proxy"
@@ -18,7 +19,7 @@ import (
func Test_closingProxyDeletesTempCert(t *testing.T) {
debugLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)

wp, err := proxy.NewWrapperProxy("", "", debugLogger)
wp, err := proxy.NewWrapperProxy("", "", "", debugLogger)
assert.Nil(t, err)

port, err := wp.Start()
@@ -35,7 +36,7 @@ func Test_closingProxyDeletesTempCert(t *testing.T) {
func Test_canGoThroughProxy(t *testing.T) {
debugLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)

wp, err := proxy.NewWrapperProxy("", "", debugLogger)
wp, err := proxy.NewWrapperProxy("", "", "", debugLogger)
assert.Nil(t, err)

port, err := wp.Start()
@@ -76,3 +77,63 @@ func Test_canGoThroughProxy(t *testing.T) {
_, err = os.Stat(wp.CertificateLocation)
assert.NotNil(t, err) // this means the file is gone
}

func Test_xSnykCliVersionHeaderIsReplaced(t *testing.T) {
debugLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)

expectedVersion := "the-cli-version"
wp, err := proxy.NewWrapperProxy("", "", expectedVersion, debugLogger)
assert.Nil(t, err)

port, err := wp.Start()
assert.Nil(t, err)
t.Log("proxy listening on port:", port)

rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}

proxyCertBytes, err := ioutil.ReadFile(wp.CertificateLocation)
assert.Nil(t, err)
ok := rootCAs.AppendCertsFromPEM(proxyCertBytes)
assert.True(t, ok)

config := &tls.Config{
InsecureSkipVerify: false,
RootCAs: rootCAs,
}

proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", port))
proxiedClient := &http.Client{Transport: &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
TLSClientConfig: config,
}}
assert.Nil(t, err)

var capturedVersion string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedVersion = r.Header.Get("x-snyk-cli-version")
}))
defer ts.Close()

// request without the header set
res, err := proxiedClient.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, "", capturedVersion)

// request with the header set
req, _ := http.NewRequest("GET", ts.URL, nil)
req.Header.Add("x-snyk-cli-version", "1.0.0")
res, err = proxiedClient.Do(req)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, expectedVersion, capturedVersion)

wp.Close()
}
4 changes: 2 additions & 2 deletions src/cli/commands/test/iac/index.ts
Original file line number Diff line number Diff line change
@@ -277,7 +277,7 @@ export default async function(
let response = '';

if (isNewIacOutputSupported && !notSuccess) {
response += getIacDisplayedIssues(results, iacOutputMeta!);
response += EOL + getIacDisplayedIssues(results, iacOutputMeta!);
} else {
response += results
.map((result, i) => {
@@ -307,7 +307,7 @@ export default async function(
errorResultsLength = iacScanFailures.length || errorResults.length;

response += isNewIacOutputSupported
? EOL + formatIacTestFailures(iacScanFailures)
? EOL.repeat(2) + formatIacTestFailures(iacScanFailures)
: iacScanFailures
.map((reason) => chalk.bold.red(getIacDisplayErrorFileOutput(reason)))
.join('');
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ export function formatScanResults(
try {
const groupedByFile = scanResults.reduce((memo, scanResult) => {
const res = formatScanResult(scanResult, meta, options);

if (memo[scanResult.filePath]) {
memo[scanResult.filePath].result.cloudConfigResults.push(
...res.result.cloudConfigResults,
28 changes: 0 additions & 28 deletions src/lib/formatters/iac-output/v2/formatters.ts

This file was deleted.

58 changes: 0 additions & 58 deletions src/lib/formatters/iac-output/v2/issues-list.ts

This file was deleted.

29 changes: 29 additions & 0 deletions src/lib/formatters/iac-output/v2/issues-list/formatters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FormattedResult } from '../../../../../cli/commands/test/iac/local-execution/types';
import { IacOutputMeta } from '../../../../types';
import { IacTestOutput } from './types';

export function formatScanResultsNewOutput(
oldFormattedResults: FormattedResult[],
outputMeta: IacOutputMeta,
): IacTestOutput {
const newFormattedResults: IacTestOutput = {
results: {},
metadata: outputMeta,
};

oldFormattedResults.forEach((oldFormattedResult) => {
oldFormattedResult.result.cloudConfigResults.forEach((issue) => {
if (!newFormattedResults.results[issue.severity]) {
newFormattedResults.results[issue.severity] = [];
}

newFormattedResults.results[issue.severity].push({
issue,
targetFile: oldFormattedResult.targetFile,
projectType: oldFormattedResult.result.projectType,
});
});
});

return newFormattedResults;
}
52 changes: 52 additions & 0 deletions src/lib/formatters/iac-output/v2/issues-list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { EOL } from 'os';
import * as capitalize from 'lodash.capitalize';
import * as isEmpty from 'lodash.isempty';
import * as debug from 'debug';

import { IacOutputMeta } from '../../../../types';
import { colors } from '../color-utils';
import { formatScanResultsNewOutput } from './formatters';
import { formatIssue } from './issue';
import { SEVERITY } from '../../../../snyk-test/common';
import { FormattedOutputResult } from './types';
import { FormattedResult } from '../../../../../cli/commands/test/iac/local-execution/types';

export function getIacDisplayedIssues(
results: FormattedResult[],
outputMeta: IacOutputMeta,
): string {
const titleOutput = colors.info.bold('Issues');

const formattedResults = formatScanResultsNewOutput(results, outputMeta);

if (isEmpty(formattedResults.results)) {
return (
titleOutput +
EOL +
' '.repeat(2) +
colors.success.bold('No vulnerable paths were found!')
);
}

const severitySectionsOutput = Object.values(SEVERITY)
.filter((severity) => !!formattedResults.results[severity])
.map((severity) => {
const severityResults: FormattedOutputResult[] =
formattedResults.results[severity];

const titleOutput = colors.severities[severity](
`${capitalize(severity)} Severity Issues: ${severityResults.length}`,
);

const issuesOutput = severityResults.map(formatIssue).join(EOL.repeat(2));

debug(
`iac display output - ${severity} severity ${severityResults.length} issues`,
);

return titleOutput + EOL.repeat(2) + issuesOutput;
})
.join(EOL.repeat(2));

return titleOutput + EOL.repeat(2) + severitySectionsOutput;
}
59 changes: 59 additions & 0 deletions src/lib/formatters/iac-output/v2/issues-list/issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as capitalize from 'lodash.capitalize';
import chalk from 'chalk';
import { EOL } from 'os';
import { iacRemediationTypes } from '../../../../iac/constants';

import { printPath } from '../../../remediation-based-format-issues';
import { colors } from '../color-utils';
import { FormattedOutputResult } from './types';
import { AnnotatedIacIssue } from '../../../../snyk-test/iac-test-result';

export function formatIssue(result: FormattedOutputResult): string {
const titleOutput = formatTitle(result.issue);

const propertiesOutput = formatProperties(result);

return titleOutput + EOL + propertiesOutput;
}

function formatTitle(issue: AnnotatedIacIssue): string {
const severity = issue.severity;
const titleOutput = colors.severities[severity](
`[${capitalize([issue.severity])}] ${chalk.bold(issue.title)}`,
);

return titleOutput;
}

function formatProperties(result: FormattedOutputResult): string {
const remediationKey = iacRemediationTypes?.[result.projectType];

const properties = [
[
'Info',
`${result.issue.iacDescription.issue}${
result.issue.iacDescription.issue.endsWith('.') ? '' : '.'
} ${result.issue.iacDescription.impact}`,
],
['Rule', result.issue.documentation],
['Path', printPath(result.issue.cloudConfigPath, 0)],
['File', result.targetFile],
[
'Resolve',
remediationKey && result.issue.remediation?.[remediationKey]
? result.issue.remediation[remediationKey]
: result.issue.iacDescription.resolve,
],
].filter(([, val]) => !!val) as [string, string][];

const maxPropertyNameLength = Math.max(
...properties.map(([key]) => key.length),
);

return properties
.map(
([key, value]) =>
`${key}: ${' '.repeat(maxPropertyNameLength - key.length)}${value}`,
)
.join(EOL);
}
19 changes: 19 additions & 0 deletions src/lib/formatters/iac-output/v2/issues-list/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IacProjectType } from '../../../../iac/constants';
import { SEVERITY } from '../../../../snyk-test/common';
import { AnnotatedIacIssue } from '../../../../snyk-test/iac-test-result';
import { IacOutputMeta } from '../../../../types';

export type FormattedOutputResult = {
issue: AnnotatedIacIssue;
targetFile: string;
projectType: IacProjectType;
};

export type FormattedOutputResultsBySeverity = {
[severity in keyof SEVERITY]?: FormattedOutputResult[];
};

export type IacTestOutput = {
results: FormattedOutputResultsBySeverity;
metadata: IacOutputMeta;
};
19 changes: 0 additions & 19 deletions src/lib/formatters/iac-output/v2/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
import { IacTestResponse } from '../../../snyk-test/iac-test-result';
import { PolicyMetadata } from '../../../../cli/commands/test/iac/local-execution/types';
import { SEVERITY } from '../../../snyk-test/common';
import { IacOutputMeta } from '../../../types';

export interface IacTestData {
ignoreCount: number;
results: IacTestResponse[];
}

export type FormattedIssue = {
policyMetadata: PolicyMetadata;
// Decide which one of them to keep
targetFile: string;
targetFilePath: string;
};

type FormattedResultsBySeverity = {
[severity in keyof SEVERITY]?: FormattedIssue[];
};

export type IacTestOutput = {
results: FormattedResultsBySeverity;
metadata: IacOutputMeta;
};
7 changes: 7 additions & 0 deletions src/lib/iac/constants.ts
Original file line number Diff line number Diff line change
@@ -26,3 +26,10 @@ export const TEST_SUPPORTED_IAC_PROJECTS: IacProjectTypes[] = [
IacProjectType.MULTI_IAC,
IacProjectType.CUSTOM,
];

export const iacRemediationTypes: { [k in IacProjectTypes]?: string } = {
armconfig: 'arm',
cloudformationconfig: 'cloudformation',
k8sconfig: 'kubernetes',
terraformconfig: 'terraform',
};
2 changes: 1 addition & 1 deletion src/lib/iac/drift/driftctl.ts
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ const generateScanFlags = (
args.push('--strict');
}

if (options.deep) {
if (options.deep || options.all) {
args.push('--deep');
}

4 changes: 4 additions & 0 deletions src/lib/snyk-test/iac-test-result.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,10 @@ export interface AnnotatedIacIssue {
path?: string[];
documentation?: string;
isGeneratedByCustomRule?: boolean;
remediation?: Partial<
Record<'terraform' | 'cloudformation' | 'arm' | 'kubernetes', string>
>;

// Legacy fields from Registry, unused.
name?: string;
from?: string[];
8 changes: 0 additions & 8 deletions test/jest/acceptance/cli-args.spec.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ import { UnsupportedOptionCombinationError } from '../../../src/lib/errors/unsup
import { runSnykCLI } from '../util/runSnykCLI';
import { fakeServer } from '../../acceptance/fake-server';
import { createProject } from '../util/createProject';
import { isCLIV2 } from '../util/isCLIV2';

const isWindows =
require('os-name')()
@@ -13,13 +12,6 @@ const isWindows =
jest.setTimeout(1000 * 60 * 5);

describe('cli args', () => {
if (isCLIV2()) {
// eslint-disable-next-line jest/no-focused-tests
it.only('CLIv2 not yet supported', () => {
console.warn('Skipping test as CLIv2 does not support it yet.');
});
}

let server;
let env: Record<string, string>;

6 changes: 3 additions & 3 deletions test/jest/acceptance/iac/describe.spec.ts
Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ describe('iac describe', () => {
// First invocation of driftctl scan triggered by describe cmd
expect(output).toContain('DCTL_IS_SNYK=true');
expect(output).toContain(
`ARGS=scan --no-version-check --output json://stdout --config-dir ${paths.cache} --to aws+tf`,
`ARGS=scan --no-version-check --output json://stdout --deep --config-dir ${paths.cache} --to aws+tf`,
);

// no second invocation with console output
@@ -129,7 +129,7 @@ describe('iac describe', () => {
// First invocation of driftctl scan triggered by describe cmd
expect(output).toContain('DCTL_IS_SNYK=true');
expect(output).toContain(
`ARGS=scan --no-version-check --output json://stdout --config-dir ${paths.cache} --to aws+tf`,
`ARGS=scan --no-version-check --output json://stdout --deep --config-dir ${paths.cache} --to aws+tf`,
);

// Second invocation of driftctl fmt triggered by describe cmd
@@ -180,7 +180,7 @@ describe('iac describe', () => {
// First invocation of driftctl scan triggered by describe cmd
expect(output).toContain('DCTL_IS_SNYK=true');
expect(output).toContain(
`ARGS=scan --no-version-check --output json://stdout --config-dir ${paths.cache} --to aws+tf`,
`ARGS=scan --no-version-check --output json://stdout --deep --config-dir ${paths.cache} --to aws+tf`,
);

// Second invocation of driftctl fmt triggered by describe cmd
18 changes: 16 additions & 2 deletions test/jest/acceptance/iac/iac-output.spec.ts
Original file line number Diff line number Diff line change
@@ -43,11 +43,25 @@ describe('iac test output', () => {
);
});

it('should show a subtitle for medium severity issues', async () => {
it('should show the issues list section with correct values', async () => {
const { stdout } = await run('snyk iac test ./iac/arm/rule_test.json');

expect(stdout).toContain(
'Issues' + EOL.repeat(2) + 'Medium Severity Issues: 1',
'Issues' +
EOL.repeat(2) +
'Medium Severity Issues: 1' +
EOL.repeat(2) +
'[Medium] Azure Firewall Network Rule Collection allows public access' +
EOL +
'Info: That inbound traffic is allowed to a resource from any source instead of a restricted range. That potentially everyone can access your resource' +
EOL +
'Rule: https://snyk.io/security-rules/SNYK-CC-TF-20' +
EOL +
'Path: resources[1] > properties > networkRuleCollections[0] > properties > rules[0] > sourceAddresses' +
EOL +
'File: ./iac/arm/rule_test.json' +
EOL +
'Resolve: Set `properties.networkRuleCollections.properties.rules.sourceAddresses` attribute to specific IP range only, e.g. `192.168.1.0/24`',
);
});

1,686 changes: 843 additions & 843 deletions test/jest/unit/iac/process-results/fixtures/new-output-formatted-results.json

Large diffs are not rendered by default.

93 changes: 0 additions & 93 deletions test/jest/unit/lib/formatters/iac-output/v2/issues-list.spec.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as path from 'path';
import * as fs from 'fs';
import { formatScanResultsNewOutput } from '../../../../../../../src/lib/formatters/iac-output/v2/formatters';
import { FormattedResult } from '../../../../../../../src/cli/commands/test/iac/local-execution/types';
import { IacOutputMeta } from '../../../../../../../src/lib/types';
import { IacTestOutput } from '../../../../../../../src/lib/formatters/iac-output/v2/types';
import { IacOutputMeta } from '../../../../../../../../src/lib/types';
import { formatScanResultsNewOutput } from '../../../../../../../../src/lib/formatters/iac-output/v2/issues-list/formatters';
import { FormattedResult } from '../../../../../../../../src/cli/commands/test/iac/local-execution/types';
import { IacTestOutput } from '../../../../../../../../src/lib/formatters/iac-output/v2/issues-list/types';

describe('IaC Output Mapper', () => {
const fixtureContent = fs.readFileSync(
@@ -13,6 +13,7 @@ describe('IaC Output Mapper', () => {
'..',
'..',
'..',
'..',
'iac',
'process-results',
'fixtures',
@@ -29,6 +30,7 @@ describe('IaC Output Mapper', () => {
'..',
'..',
'..',
'..',
'iac',
'process-results',
'fixtures',
194 changes: 194 additions & 0 deletions test/jest/unit/lib/formatters/iac-output/v2/issues-list/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import * as fs from 'fs';
import * as pathLib from 'path';
import chalk from 'chalk';
import { IacOutputMeta } from '../../../../../../../../src/lib/types';
import { getIacDisplayedIssues } from '../../../../../../../../src/lib/formatters/iac-output';
import { colors } from '../../../../../../../../src/lib/formatters/iac-output/v2/color-utils';
import { FormattedResult } from '../../../../../../../../src/cli/commands/test/iac/local-execution/types';

describe('getIacDisplayedIssues', () => {
let resultFixtures: FormattedResult[];
const outputMeta: IacOutputMeta = {
orgName: 'Shmulik.Kipod',
projectName: 'project-name',
};

beforeAll(async () => {
resultFixtures = JSON.parse(
fs.readFileSync(
pathLib.join(
__dirname,
'..',
'..',
'..',
'..',
'..',
'iac',
'process-results',
'fixtures',
'formatted-results.json',
),
'utf8',
),
);
});

it("should include the 'Issues' title", () => {
const result = getIacDisplayedIssues(resultFixtures, outputMeta);

expect(result).toContain(colors.info.bold('Issues'));
});

it('should include a subtitle for each severity with the correct amount of issues', () => {
const result = getIacDisplayedIssues(resultFixtures, outputMeta);

expect(result).toContain(colors.severities.low(`Low Severity Issues: 13`));
expect(result).toContain(
colors.severities.medium(`Medium Severity Issues: 4`),
);
expect(result).toContain(colors.severities.high(`High Severity Issues: 5`));
});

it('should include the correct issues in each severity section, and display the correct issue details', () => {
// Arrange
const testFormattedResults = resultFixtures.slice(2, 4);

// Act
const result = getIacDisplayedIssues(testFormattedResults, outputMeta);

// Assert
expect(result).toContain(
`${colors.severities.low(
`[Low] ${chalk.bold('Container could be running with outdated image')}`,
)}
Info: The image policy does not prevent image reuse. The container may run with outdated or unauthorized image
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-42
Path: [DocId: 0] > spec > template > spec > containers[web] > imagePullPolicy
File: k8s.yaml
Resolve: Set \`imagePullPolicy\` attribute to \`Always\`
${colors.severities.low(
`[Low] ${chalk.bold('Container is running with writable root filesystem')}`,
)}
Info: \`readOnlyRootFilesystem\` attribute is not set to \`true\`. Compromised process could abuse writable root filesystem to elevate privileges
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-8
Path: [DocId: 0] > input > spec > template > spec > containers[web] > securityContext > readOnlyRootFilesystem
File: k8s.yaml
Resolve: Set \`securityContext.readOnlyRootFilesystem\` to \`true\`
${colors.severities.low(
`[Low] ${chalk.bold('Container is running without AppArmor profile')}`,
)}
Info: The AppArmor profile is not set correctly. AppArmor will not enforce mandatory access control, which can increase the attack vectors.
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-32
Path: [DocId: 0] > metadata > annotations['container.apparmor.security.beta.kubernetes.io/web']
File: k8s.yaml
Resolve: Add \`container.apparmor.security.beta.kubernetes.io/<container-name>\` annotation with value \`runtime/default\` or \`localhost/<name-of-profile\`
${colors.severities.low(
`[Low] ${chalk.bold('Container is running without memory limit')}`,
)}
Info: Memory limit is not defined. Containers without memory limits are more likely to be terminated when the node runs out of memory
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-4
Path: [DocId: 0] > input > spec > template > spec > containers[web] > resources > limits > memory
File: k8s.yaml
Resolve: Set \`resources.limits.memory\` value
${colors.severities.low(
`[Low] ${chalk.bold('Container is running without cpu limit')}`,
)}
Info: CPU limit is not defined. Containers without limits can exceed the capacity of the node, and affect availability/performance of the host and other containers.
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-5
Path: [DocId: 0] > input > spec > template > spec > containers[web] > resources > limits > cpu
File: k8s.yaml
Resolve: Add \`resources.limits.cpu\` field with required CPU limit value`,
);

expect(result).toContain(
`${colors.severities.medium(
`[Medium] ${chalk.bold(
'Container is running without root user control',
)}`,
)}
Info: Container is running without root user control. Container could be running with full administrative privileges
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-10
Path: [DocId: 0] > input > spec > template > spec > containers[web] > securityContext > runAsNonRoot
File: k8s.yaml
Resolve: Set \`securityContext.runAsNonRoot\` to \`true\`
${colors.severities.medium(
`[Medium] ${chalk.bold('Container does not drop all default capabilities')}`,
)}
Info: All default capabilities are not explicitly dropped. Containers are running with potentially unnecessary privileges
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-6
Path: [DocId: 0] > input > spec > template > spec > containers[web] > securityContext > capabilities > drop
File: k8s.yaml
Resolve: Add \`ALL\` to \`securityContext.capabilities.drop\` list, and add only required capabilities in \`securityContext.capabilities.add\`
${colors.severities.medium(
`[Medium] ${chalk.bold(
'Container is running without privilege escalation control',
)}`,
)}
Info: \`allowPrivilegeEscalation\` attribute is not set to \`false\`. Processes could elevate current privileges via known vectors, for example SUID binaries
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-9
Path: [DocId: 0] > input > spec > template > spec > containers[web] > securityContext > allowPrivilegeEscalation
File: k8s.yaml
Resolve: Set \`securityContext.allowPrivilegeEscalation\` to \`false\``,
);

expect(result).toContain(
`${colors.severities.high(
`[High] ${chalk.bold('Container is running in privileged mode')}`,
)}
Info: Container is running in privileged mode. Compromised container could potentially modify the underlying host’s kernel by loading unauthorized modules (i.e. drivers).
Rule: https://snyk.io/security-rules/SNYK-CC-K8S-1
Path: [DocId: 0] > input > spec > template > spec > containers[web] > securityContext > privileged
File: k8s.yaml
Resolve: Remove \`securityContext.privileged\` attribute, or set value to \`false\``,
);
});

describe('with no issues', () => {
let resultsWithNoIssues: FormattedResult[];

beforeAll(() => {
resultsWithNoIssues = resultFixtures.map((resultFixture) => ({
...resultFixture,
result: {
...resultFixture.result,
cloudConfigResults: [],
},
}));
});

it('should display an appropriate message', () => {
// Act
const result = getIacDisplayedIssues(resultsWithNoIssues, outputMeta);

// Assert
expect(result).toContain(
colors.success.bold('No vulnerable paths were found!'),
);
});

it('should not display any severity sections', () => {
// Act
const result = getIacDisplayedIssues(resultsWithNoIssues, outputMeta);

// Assert
expect(result).not.toContain(
colors.severities.low('Low Severity Issues'),
);
expect(result).not.toContain(
colors.severities.medium('Medium Severity Issues'),
);
expect(result).not.toContain(
colors.severities.high('High Severity Issues'),
);
expect(result).not.toContain(
colors.severities.critical('Critical Severity Issues'),
);
});
});
});
38 changes: 38 additions & 0 deletions test/jest/unit/lib/iac/drift.spec.ts
Original file line number Diff line number Diff line change
@@ -52,6 +52,44 @@ describe('driftctl integration', () => {
]);
});

it('describe: --all enable deep mode', () => {
{
const args = generateArgs(
{ kind: 'describe', all: true } as DescribeOptions,
[],
);
expect(args).toEqual([
'scan',
'--no-version-check',
'--output',
'json://stdout',
'--deep',
'--config-dir',
paths.cache,
'--to',
'aws+tf',
]);
}

{
const args = generateArgs(
{ kind: 'describe', all: true, deep: true } as DescribeOptions,
[],
);
expect(args).toEqual([
'scan',
'--no-version-check',
'--output',
'json://stdout',
'--deep',
'--config-dir',
paths.cache,
'--to',
'aws+tf',
]);
}
});

it('describe: passing options generate correct arguments', () => {
const args = generateArgs(
{