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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
68 changes: 39 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down Expand Up @@ -45,6 +45,10 @@
"icon": "$(refresh)",
"category": "Spectral"
},
{
"command": "spectral.install",
"title": "Install Spectral"
},
{
"command": "spectral.showOutput",
"title": "Show Output Channel"
Expand Down Expand Up @@ -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"
},
{
Expand Down Expand Up @@ -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"
}
}
}
},
Expand All @@ -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",
Expand Down
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,
Expand Down Expand Up @@ -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
Expand Down
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'
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/services/context-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/services/secret-storage-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
36 changes: 35 additions & 1 deletion src/services/spectral-agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FindingSeverity,
FindingType,
severityMapping,
SPECTRAL_BASE_URL,
SPECTRAL_DSN,
SPECTRAL_FOLDER,
} from '../common/constants'
Expand All @@ -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'

Expand All @@ -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'], {
Expand Down
Loading

0 comments on commit 748cd7a

Please sign in to comment.