Skip to content

Commit

Permalink
Spectral install and update (#9)
Browse files Browse the repository at this point in the history
* spectral install and update, dsn validation
  • Loading branch information
guylev008 authored Jul 12, 2023
1 parent 63316a2 commit 748cd7a
Showing 15 changed files with 385 additions and 331 deletions.
3 changes: 2 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -16,4 +16,5 @@
# Checklist
- [ ] Tests
- [ ] Documentation
- [ ] Linting
- [ ] Linting
- [ ] Change log
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# SpectralOps - Automated Code Security Change Log
## [1.1.1]

- Install Spectral from the extension
- Auto update Spectral
## [1.1.0]

- Support user configuration for 'engines' and 'includesTags' flags
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -34,14 +34,14 @@ Read more about our mission statement [here](https://spectralops.io/).

After you've installed the extension, you'll see a new icon in the activity bar.

First, you'll now need to fill in your Spectral DSN. Additionally, you'll need the Spectral binary in your PATH. The extension will guide you through those steps - read on to learn more.
First, you'll now need to download Spectral binary. Additionally, you'll need to fill in your Spectral DSN. The extension will guide you through those steps - read on to learn more.

## Configuration

- Sign up and get your SpectralOps account [here.](https://get.spectralops.io/signup) If you already have an account, sign in and do the next step.
- Go to our docs in the bottom left menu and follow the instructions on downloading Spectral binary.
- From Settings -> Organization, copy your DSN.
- In Visual Studio Code, set your DSN in the SpectralOps extension.
- Set your DSN in the SpectralOps extension.
- In the extension configuration set the engines you would like to run and tags to include.

## Usage

@@ -55,7 +55,7 @@ The Spectral DSN (Data Source Name) is your personal key to communicate with Spe

#### Spectral binary

This extension requires the Spectral binary to be present and available in your PATH. You can install it by following the instructions in our docs.
This extension requires the Spectral binary to be present and available. You can install it from the extension or by following the instructions in our docs. The extension will automatically update Spectral agent, if you wish to disable it you can do it from the extension configuration.

### How to Contribute

68 changes: 39 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"name": "spectral-vscode-extension",
"displayName": "SpectralOps - A Check Point Solution",
"description": "Monitor your code for exposed API keys, tokens, credentials, and high-risk security misconfigurations",
"version": "1.1.0",
"version": "1.1.1",
"publisher": "SpectralOps",
"icon": "media/spectral.png",
"homepage": "https://spectralops.io/",
@@ -45,6 +45,10 @@
"icon": "$(refresh)",
"category": "Spectral"
},
{
"command": "spectral.install",
"title": "Install Spectral"
},
{
"command": "spectral.showOutput",
"title": "Show Output Channel"
@@ -102,7 +106,7 @@
},
{
"view": "spectral.views.welcome",
"contents": "Welcome to Spectral for Visual Studio Code. We noticed that Spectral is not installed on your machine. \nPlease refer to our docs for more details on [how to install Spectral](https://guides.spectralops.io/docs/how-to-get-started).",
"contents": "Welcome to Spectral for Visual Studio Code. We noticed that Spectral is not installed on your machine. \n[Install Spectral](command:spectral.install)\n \nYou can also refer to our docs for more details on [how to install Spectral manually](https://guides.spectralops.io/docs/how-to-get-started)",
"when": "!spectral:hasSpectralInstalled"
},
{
@@ -149,34 +153,40 @@
"type": "object",
"title": "Spectral",
"properties": {
"spectral.scan.engines.useSecretsEngine": {
"type": "boolean",
"default": true,
"description": "Scan for secrets",
"scope": "resource"
},
"spectral.scan.engines.useIacEngine": {
"type": "boolean",
"default": false,
"description": "Scan for infrastructure as code",
"scope": "resource"
"spectral.scan.engines.useSecretsEngine": {
"type": "boolean",
"default": true,
"description": "Scan for secrets",
"scope": "window"
},
"spectral.scan.engines.useIacEngine": {
"type": "boolean",
"default": false,
"description": "Scan for infrastructure as code",
"scope": "window"
},
"spectral.scan.engines.useOssEngine": {
"type": "boolean",
"default": false,
"description": "Scan open source packages",
"scope": "window"
},
"spectral.scan.includeTags": {
"type": "array",
"items": {
"type": "string"
},
"spectral.scan.engines.useOssEngine": {
"type": "boolean",
"default": false,
"description": "Scan open source packages",
"scope": "resource"
"maxItems": 100,
"default": [],
"description": "Scan include tags",
"scope": "window"
},
"spectral.scan.includeTags": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 100,
"default": [],
"description": "Scan include tags",
"scope": "resource"
}
"spectral.install.autoUpdate": {
"type": "boolean",
"default": true,
"description": "Auto update Spectral agent",
"scope": "window"
}
}
}
},
@@ -200,7 +210,7 @@
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^4.26.0",
"esbuild": "^0.15.7",
"eslint": "^7.27.0",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"glob": "^7.1.7",
5 changes: 5 additions & 0 deletions src/common/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WorkspaceConfiguration } from 'vscode'
import {
AUTO_UPDATE_SETTINGS,
CONFIGURATION_IDENTIFIER,
INCLUDE_TAGS_SETTING,
ScanEngine,
@@ -51,6 +52,10 @@ export class Configuration {
.join(',')
}

get isAutoUpdateEnabled() {
return this.extensionConfig.get<boolean>(AUTO_UPDATE_SETTINGS)
}

public updateConfiguration = (workspace) => {
const extensionConfig = workspace.getConfiguration(CONFIGURATION_IDENTIFIER)
this.extensionConfig = extensionConfig
9 changes: 6 additions & 3 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export const SPECTRAL_SCAN = 'spectral.scan'
export const SPECTRAL_INSTALL = 'spectral.install'
export const SPECTRAL_SHOW_OUTPUT = 'spectral.showOutput'
export const SPECTRAL_SET_DSN = 'spectral.setDsn'
export const CONTEXT_PREFIX = 'spectral:'
export const HAS_SPECTRAL_INSTALLED = 'hasSpectralInstalled'
export const HAS_DSN = 'hasDsn'
export const HAS_LICENSE = 'hasLicense'
export const SCAN_STATE = 'scanState'
export const ENABLE_INSTALL_AGENT = 'enableInstallAgent'
export const PRE_SCAN = 'preScan'
export const SPECTRAL_VIEW_SECRETS = 'spectral.views.secrets'
export const SPECTRAL_VIEW_IAC = 'spectral.views.iac'
@@ -59,11 +61,12 @@ export const SPECTRAL_FOLDER = `${process.env.HOME}/.spectral`
export const SPECTRAL_DSN = 'SPECTRAL_DSN'
export const FINDING_POSITION_LINE_INDEX = 0
export const FINDING_POSITION_COL_INDEX = 1
export const PLAYBOOKS_URL =
'https://get.spectralops.io/api/v1/issues/playbooks'

export const SPECTRAL_BASE_URL = 'https://get.spectralops.io'
export const PLAYBOOKS_URL = `${SPECTRAL_BASE_URL}/api/v1/issues/playbooks`
export const AGENT_LAST_UPDATE_DATE = 'spectral.agentLastUpdateDate'
export const CONFIGURATION_IDENTIFIER = 'spectral'
export const USE_IAC_ENGINE_SETTING = 'scan.engines.useIacEngine'
export const USE_OSS_ENGINE_SETTING = 'scan.engines.useOssEngine'
export const USE_SECRET_ENGINE_SETTING = 'scan.engines.useSecretsEngine'
export const INCLUDE_TAGS_SETTING = 'scan.includeTags'
export const AUTO_UPDATE_SETTINGS = 'install.autoUpdate'
31 changes: 31 additions & 0 deletions src/common/persistence-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as vscode from 'vscode'

export class PersistenceContext {
private context?: vscode.ExtensionContext
private static instance: PersistenceContext

public static getInstance() {
if (!this.instance) {
this.instance = new PersistenceContext()
}

return this.instance
}

setContext(context: vscode.ExtensionContext): void {
this.context = context
}

getGlobalStateValue<T>(key: string): T | undefined {
return this.acquireContext().globalState.get(key)
}

updateGlobalStateValue(key: string, value: unknown): Thenable<void> {
return this.acquireContext().globalState.update(key, value)
}

private acquireContext(): vscode.ExtensionContext {
if (!this.context) throw new Error('VS Code extension context not set.')
return this.context
}
}
21 changes: 21 additions & 0 deletions src/common/spectral-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Configuration } from './configuration'
import { AGENT_LAST_UPDATE_DATE } from './constants'
import { PersistenceContext } from './persistence-context'

export const shouldUpdateSpectralAgent = (): boolean => {
return (
isOverUpdateThreshold() && Configuration.getInstance().isAutoUpdateEnabled
)
}

const isOverUpdateThreshold = (): boolean => {
const lastUpdateDate =
PersistenceContext.getInstance().getGlobalStateValue<number>(
AGENT_LAST_UPDATE_DATE
)
if (!lastUpdateDate) {
return true
}
const oneWeekInMs = 7 * 24 * 3600 * 1000
return Date.now() - lastUpdateDate > oneWeekInMs
}
17 changes: 14 additions & 3 deletions src/common/vs-code.ts
Original file line number Diff line number Diff line change
@@ -24,8 +24,8 @@ import {
} from './constants'
import { Findings, ScanFinding, ScanFindingView } from './types'

export const setContext = async (key: string, value: any): Promise<void> => {
await commands.executeCommand('setContext', `${CONTEXT_PREFIX}${key}`, value)
export const setContext = (key: string, value: any): void => {
commands.executeCommand('setContext', `${CONTEXT_PREFIX}${key}`, value)
}

export const getActiveTextEditor = () => window.activeTextEditor
@@ -119,13 +119,24 @@ type InputBoxOptions = {

export const showInputBox = (
options: InputBoxOptions,
task: (value) => Promise<void>
task: (value) => Promise<void>,
inputValidation?: (value) => boolean,
validationMessage?: string
) => {
window
.showInputBox({
password: options.password,
placeHolder: options.placeHolder,
title: options.title,
validateInput(value) {
if (inputValidation) {
const isValid = inputValidation(value)
if (!isValid) {
return validationMessage
}
}
return null
},
})
.then(async (value) => {
if (value) {
4 changes: 2 additions & 2 deletions src/services/context-service.ts
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@ export class ContextService {
return this.viewContext[key]
}

public async setContext(key: string, value: any): Promise<void> {
public setContext(key: string, value: any): void {
this.viewContext[key] = value
await setContext(key, value)
setContext(key, value)
}

get scanState(): string {
4 changes: 2 additions & 2 deletions src/services/secret-storage-service.ts
Original file line number Diff line number Diff line change
@@ -15,8 +15,8 @@ export default class SecretStorageService {
return this.secretStorage.get(key) as Promise<string | undefined>
}

store(key: string, value: string): Promise<void> {
return this.secretStorage.store(key, value) as Promise<void>
store(key: string, value: string): void {
this.secretStorage.store(key, value) as Promise<void>
}

delete(key: string): Promise<void> {
36 changes: 35 additions & 1 deletion src/services/spectral-agent-service.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
FindingSeverity,
FindingType,
severityMapping,
SPECTRAL_BASE_URL,
SPECTRAL_DSN,
SPECTRAL_FOLDER,
} from '../common/constants'
@@ -16,7 +17,6 @@ import {
ScanResult,
} from '../common/types'
import { formatWindowsPath, isWindows } from '../common/utils'

import SecretStorageService from './secret-storage-service'
import { Configuration } from '../common/configuration'

@@ -28,6 +28,40 @@ export class SpectralAgentService {
this.resetFindings()
}

public installSpectral(): Promise<any> {
return new Promise((resolve, reject) => {
let child
let command
if (isWindows()) {
command = `iwr ${SPECTRAL_BASE_URL}/latest/ps1 -useb | iex`
child = spawn('powershell.exe', [command])
} else {
command = `curl -L '${SPECTRAL_BASE_URL}/latest/x/sh' | sh`
child = spawn('/bin/sh', ['-c', command])
}

child.stderr.setEncoding('utf8')
const stdOut: string[] = []
const stderrChunks: string[] = []
child.stdout.on('data', (data) => {
stdOut.push(data.toString('utf8'))
})
child.stderr.on('data', (chunk) => {
return stderrChunks.push(chunk)
})
child.on('error', async (err) => {
reject(err)
})
child.on('close', (code) => {
if (!isEmpty(stderrChunks) && code !== 0) {
const error = stderrChunks.join('')
return reject(error)
}
return resolve('')
})
})
}

public checkForSpectralBinary(): Promise<Boolean> {
return new Promise((resolve) => {
const child = spawn(this.getSpectralPath(), ['--nobanners', 'version'], {
Loading

0 comments on commit 748cd7a

Please sign in to comment.