Skip to content

Commit

Permalink
golang HTTP API (#654)
Browse files Browse the repository at this point in the history
Golang HTTP API
Implement v3 

---------

Co-authored-by: Philip Stadermann <[email protected]>
Co-authored-by: robin.hubbig <[email protected]>
Co-authored-by: Verdict-as-a-Service Team <[email protected]>
Co-authored-by: Kevin Heise <[email protected]>
  • Loading branch information
5 people authored Nov 29, 2024
1 parent 6a28bdf commit e00a463
Show file tree
Hide file tree
Showing 25 changed files with 2,709 additions and 5 deletions.
29 changes: 24 additions & 5 deletions .github/workflows/ci-golang.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,47 @@ jobs:
image: golang:latest
strategy:
matrix:
version-directory: ["./", "v2/"]
version-directory: ["./", "v2/", "v3/"]
steps:
- uses: actions/checkout@v4

- name: set legacy vaas gateway for production
run: |
if [ "${{ matrix.version-directory }}" = "./" -o "${{ matrix.version-directory }}" = "v2/" ]; then
echo "VAAS_URL=wss://gateway.production.vaas.gdatasecurity.de" >> $GITHUB_ENV
else
echo "VAAS_URL=https://gateway.production.vaas.gdatasecurity.de" >> $GITHUB_ENV
fi
- name: set staging environment
if: (inputs.environment == 'staging' || (startsWith(github.ref, 'refs/tags') && endsWith(github.ref, '-beta')))
run: |
echo "Beta version: Testing against staging"
echo "CLIENT_ID=${{ secrets.STAGING_CLIENT_ID }}" >> $GITHUB_ENV
echo "CLIENT_SECRET=${{ secrets.STAGING_CLIENT_SECRET }}" >> $GITHUB_ENV
echo "VAAS_URL=wss://gateway.staging.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "TOKEN_URL=https://account-staging.gdata.de/realms/vaas-staging/protocol/openid-connect/token" >> $GITHUB_ENV
if [ "${{ matrix.version-directory }}" = "./" -o "${{ matrix.version-directory }}" = "v2/" ]; then
echo "VAAS_URL=wss://gateway.staging.vaas.gdatasecurity.de" >> $GITHUB_ENV
else
echo "VAAS_URL=https://gateway.staging.vaas.gdatasecurity.de" >> $GITHUB_ENV
fi
echo "VAAS_CLIENT_ID=${{ secrets.STAGING_VAAS_CLIENT_ID }}" >> $GITHUB_ENV
echo "VAAS_USER_NAME=${{ secrets.STAGING_VAAS_USER_NAME }}" >> $GITHUB_ENV
echo "VAAS_PASSWORD=${{ secrets.STAGING_VAAS_PASSWORD }}" >> $GITHUB_ENV
- name: set develop environment
if: (inputs.environment == 'develop' || (startsWith(github.ref, 'refs/tags') && endsWith(github.ref, '-alpha')))
run: |
echo "Alpha version: Testing against develop"
echo "CLIENT_ID=${{ secrets.DEVELOP_CLIENT_ID }}" >> $GITHUB_ENV
echo "CLIENT_SECRET=${{ secrets.DEVELOP_CLIENT_SECRET }}" >> $GITHUB_ENV
echo "VAAS_URL=wss://gateway.develop.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "TOKEN_URL=https://account-staging.gdata.de/realms/vaas-develop/protocol/openid-connect/token" >> $GITHUB_ENV
if [ "${{ matrix.version-directory }}" = "./" -o "${{ matrix.version-directory }}" = "v2/" ]; then
echo "VAAS_URL=wss://gateway.develop.vaas.gdatasecurity.de" >> $GITHUB_ENV
else
echo "VAAS_URL=https://gateway.develop.vaas.gdatasecurity.de" >> $GITHUB_ENV
fi
echo "VAAS_CLIENT_ID=${{ secrets.DEVELOP_VAAS_CLIENT_ID }}" >> $GITHUB_ENV
echo "VAAS_USER_NAME=${{ secrets.DEVELOP_VAAS_USER_NAME }}" >> $GITHUB_ENV
echo "VAAS_PASSWORD=${{ secrets.DEVELOP_VAAS_PASSWORD }}" >> $GITHUB_ENV
Expand Down Expand Up @@ -141,7 +160,7 @@ jobs:
image: golang:latest
strategy:
matrix:
version-directory: [".", "v2"]
version-directory: [".", "v2", "v3"]
steps:
- uses: actions/checkout@v4
- name: Install govulncheck
Expand Down Expand Up @@ -176,7 +195,7 @@ jobs:
MAJOR_VERSION: ${{ needs.extract-major-version.outputs.major_version }}
if: startsWith(github.ref, 'refs/tags')
run: |
if [ "$MAJOR_VERSION" == "v1" ]; then
if [ "$MAJOR_VERSION" = "v1" ]; then
GOPROXY=proxy.golang.org go list -m github.com/GDATASoftwareAG/vaas/golang/vaas@${GITHUB_REF#refs/tags/golang/vaas/}
else
GOPROXY=proxy.golang.org go list -m github.com/GDATASoftwareAG/vaas/golang/vaas/${MAJOR_VERSION}@${GITHUB_REF#refs/tags/golang/vaas/}
Expand Down
164 changes: 164 additions & 0 deletions golang/vaas/v3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
[![vaas-golang-ci](https://github.com/GDATASoftwareAG/vaas/actions/workflows/ci-golang.yaml/badge.svg)](https://github.com/GDATASoftwareAG/vaas/actions/workflows/ci-golang.yaml)
[![Vulnerability Check](https://github.com/GDATASoftwareAG/vaas/actions/workflows/vulncheck-golang.yml/badge.svg)](https://github.com/GDATASoftwareAG/vaas/actions/workflows/vulncheck-golang.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/GDATASoftwareAG/vaas/golang/vaas/.svg)](https://pkg.go.dev/github.com/GDATASoftwareAG/vaas/golang/vaas/)
[![Go Report Card](https://goreportcard.com/badge/github.com/GDATASoftwareAG/vaas/golang/vaas)](https://goreportcard.com/report/github.com/GDATASoftwareAG/vaas/golang/vaas)

# Go VaaS Client

This is a Golang package that provides a client for the G DATA VaaS API.

_Verdict-as-a-Service_ (VaaS) is a service that provides a platform for scanning files for malware and other threats. It allows easy integration into your application. With a few lines of code, you can start scanning files for malware.

# Table of Contents

- [What does the SDK do?](#what-does-the-sdk-do)
- [How to use](#how-to-use)
- [Installation](#installation)
- [Import](#import)
- [Authentication](#authentication)
- [Client Credentials Grant](#client-credentials-grant)
- [Resource Owner Password Grant](#resource-owner-password-grant)
- [Request a verdict](#request-a-verdict)
- [I'm interested in VaaS](#interested)
- [Developing with Visual Studio Code](#developing-with-visual-studio-code)


## What does the SDK do?

It gives you as a developer functions to talk to G DATA VaaS. It wraps away the complexity of the API into basic functions.

### Connect(ctx context.Context, auth authenticator.Authenticator) (errorChan <-chan error, err error)

Connect opens a websocket connection to the VAAS Server. Use Close() to terminate the connection. The errorChan indicates when a connection was closed. In the case of an unexpected close, an error is written to the channel.

### ForSha256(ctx context.Context, sha256 string) (messages.VaasVerdict, error)

Retrieves the verdict for the given SHA256 hash from the G DATA VaaS API. `ctx` is the context for request cancellation, and `sha256` is the SHA256 hash of the file. If the request fails, an error will be returned. Otherwise, a `messages.VaasVerdict` object containing the verdict will be returned.

### ForFile(ctx context.Context, filePath string) (messages.VaasVerdict, error)

Retrieves the verdict for the given file at the specified `filePath` from the G DATA VaaS API. `ctx` is the context for request cancellation. If the file cannot be opened, an error will be returned. Otherwise, a `messages.VaasVerdict` object containing the verdict will be returned.

### ForFileInMemory(ctx context.Context, fileData io.Reader) (messages.VaasVerdict, error)

Retrieves the verdict for file data provided as an `io.Reader` to the G DATA VaaS API. `ctx` is the context for request cancellation. If the request fails, an error will be returned. Otherwise, a `messages.VaasVerdict` object containing the verdict will be returned.

### ForUrl(ctx context.Context, url string) (messages.VaasVerdict, error)

Retrieves the verdict for the given file URL from the G DATA VaaS API. `ctx` is the context for request cancellation. If the request fails, an error will be returned. Otherwise, a `messages.VaasVerdict` object containing the verdict will be returned.

## How to use

### Installation

```sh
go get github.com/GDATASoftwareAG/vaas/golang/vaas
```

### Import

```go
import (
"github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/authenticator"
"github.com/GDATASoftwareAG/vaas/golang/vaas/pkg/vaas"
)
```

### Authentication

VaaS offers two authentication methods:

#### Client Credentials Grant
This is suitable for cases where you have a `client_id`and `client_secret`. Here's how to use it:

```go
authenticator := authenticator.New("client_id", "client_secret", "token_endpoint")
```
or
```go
authenticator := authenticator.NewWithDefaultTokenEndpoint("client_id", "client_secret")
```
#### Resource Owner Password Grant
This method is used when you have a `username` and `password`. Here's how to use it:

```go
authenticator := authenticator.NewWithResourceOwnerPassword("client_id", "username", "password", "token_endpoint")
```
If you do not have a specific Client ID, please use `"vaas-customer"` as the client_id.

### Request a verdict

Authentication & Initialization:
```go
// Create a new authenticator with the provided Client ID and Client Secret
auth := authenticator.NewWithDefaultTokenEndpoint(clientID, clientSecret)

// Create a new VaaS client with default options
vaasClient := vaas.NewWithDefaultEndpoint(options.VaasOptions{
UseHashLookup: true,
UseCache: false,
EnableLogs: false,
})

// Create a context with a cancellation function
ctx, webSocketCancel := context.WithCancel(context.Background())

// Establish a WebSocket connection to the VaaS server
errorChan, err := vaasClient.Connect(ctx, auth)
if err != nil {
log.Fatalf("failed to connect to VaaS %s", err.Error())
}
defer vaasClient.Close()

// Create a context with a timeout for the analysis
analysisCtx, analysisCancel := context.WithTimeout(context.Background(), 20*time.Second)
defer analysisCancel()
```

Verdict Request for SHA256:
```go
// Request a verdict for a specific SHA256 hash (replace "sha256-hash" with the actual SHA256 hash)
result, err := vaasClient.ForFile(analysisCtx, "sha256-hash")
if err != nil {
log.Fatalf("Failed to get verdict: %v", err)
}
fmt.Println(result.Verdict)
```

Verdict Request for a file:
```go
// Request a verdict for a specific file (replace "path-to-your-file" with the actual file path)
result, err := vaasClient.ForFile(analysisCtx, "path-to-your-file")
if err != nil {
log.Fatalf("Failed to get verdict: %v", err)
}
fmt.Printf("Verdict: %s\n", result.Verdict)
```

Verdict Request for file data provided as an io.Reader:
```go
fileData := bytes.NewReader([]byte("file contents"))
result, err := vaasClient.ForFileInMemory(analysisCtx, fileData)
if err != nil {
log.Fatalf("Failed to get verdict: %v", err)
}
fmt.Printf("Verdict: %s\n", result.Verdict)
```

Verdict Request for a file URL:
```go
result, err := vaasClient.ForUrl(analysisCtx, "https://example.com/examplefile")
if err != nil {
log.Fatalf("Failed to get verdict: %v", err)
}
fmt.Printf("Verdict: %s\n", result.Verdict)
```


## <a name="interested"></a>I'm interested in VaaS

You need credentials to use the service in your application. If you are interested in using VaaS, please [contact us](mailto:[email protected]).

## Developing with Visual Studio Code

Every single SDKs also includes [Devcontainer](./.devcontainer/). If you use the [Visual Studio Code Dev Containers extension](https://code.visualstudio.com/docs/devcontainers/containers), you can run the code in a full-featured development environment.
122 changes: 122 additions & 0 deletions golang/vaas/v3/cmd/git-scan/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"context"
"errors"
"log"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/GDATASoftwareAG/vaas/golang/vaas/v3/pkg/authenticator"
"github.com/GDATASoftwareAG/vaas/golang/vaas/v3/pkg/messages"
"github.com/GDATASoftwareAG/vaas/golang/vaas/v3/pkg/vaas"
)

func main() {
if len(os.Args) < 3 {
log.Fatal("need 2 parameter: remote, targetBranch")
}

remote := os.Args[1]
if remote == "" {
log.Fatal("no remote set")
}
log.Println("remote:", remote)
targetBranch := os.Args[2]
if targetBranch == "" {
log.Fatal("no targetBranch set")
}
log.Println("targetBranch:", targetBranch)

vaasAuthenticator, credentialsError := getAuthenticator(
os.Getenv("VAAS_CLIENT_ID"), os.Getenv("VAAS_CLIENT_SECRET"), os.Getenv("VAAS_USERAME"), os.Getenv("VAAS_PASSWORD"))
if credentialsError != nil {
log.Fatal(credentialsError)
}

vaasURLString, exists := os.LookupEnv("VAAS_URL")
if !exists {
vaasURLString = "https://gateway.production.vaas.gdatasecurity.de"
}
vaasURL, err := url.Parse(vaasURLString)
if err != nil {
log.Fatal("VAAS_URL is not an URL")
}
log.Println("vaas url:", vaasURL)

gitRevParseCommand := exec.Command("git", "rev-parse", "--show-toplevel")
rootDirectoryBytes, err := gitRevParseCommand.CombinedOutput()
if err != nil {
log.Fatal("git rev-parse: ", err, " ", string(rootDirectoryBytes))
}
rootDirectory := strings.Split(strings.ReplaceAll(string(rootDirectoryBytes), "\r\n", "\n"), "\n")[0]
log.Println("repository root directory: ", rootDirectory)

fetchBytesCommand := exec.Command("git", "fetch", remote, targetBranch)
fetchBytes, err := fetchBytesCommand.CombinedOutput()
if err != nil {
log.Fatal("git fetch ", err, " ", string(fetchBytes))
}
log.Println("fetch result: ", string(fetchBytes))

gitDiffCommand := exec.Command("git", "diff", "--name-only", remote+"/"+targetBranch)
diffBytes, err := gitDiffCommand.CombinedOutput()
if err != nil {
log.Fatal("git diff ", err, " ", string(diffBytes))
}
files := strings.Split(strings.ReplaceAll(string(diffBytes), "\r\n", "\n"), "\n")
if len(files) < 1 {
log.Println("no changed files found in diff")
os.Exit(0)
}

vaas := vaas.New(vaasURL, vaasAuthenticator)
ctx, webSocketCancel := context.WithCancel(context.Background())
var maliciousFileFound bool
for _, file := range files {
if file == "" {
continue
}
if _, err := os.Stat(file); err != nil {
continue
}
log.Println("checking file: ", file)
pathToFile := filepath.Join(rootDirectory, file)
verdict, err := vaas.ForFile(ctx, pathToFile, nil)
if err != nil {
log.Fatalln(err)
}
log.Println(pathToFile + ": " + string(verdict.Verdict))
if verdict.Verdict == messages.Malicious {
maliciousFileFound = true
}
}
webSocketCancel()
if maliciousFileFound {
os.Exit(1)
}
}

func getAuthenticator(clientId, clientSecret, username, password string) (vaasAuthenticator authenticator.Authenticator, credentialsError error) {
tokenUrl, exists := os.LookupEnv("VAAS_TOKEN_URL")
if !exists {
tokenUrl = "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
}
log.Println("token url:", tokenUrl)

if (clientId != "" && clientSecret != "") || (username != "" && password != "") {
if username != "" && password != "" {
vaasAuthenticator = authenticator.NewWithResourceOwnerPassword(username, password, "vaas-github-actions", tokenUrl)
} else {
vaasAuthenticator = authenticator.New(clientId, clientSecret, tokenUrl)
}

return

}
return nil, errors.New("you either need VAAS_CLIENT_ID and VAAS_CLIENT_SECRET or VAAS_USERAME and VAAS_PASSWORD")

}
Loading

0 comments on commit e00a463

Please sign in to comment.