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
87 changes: 44 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,55 @@
# Phase Secrets Management SDK
# Phase Secrets Management SDK for Go

The Phase Secrets SDK provides a Go package for managing secrets in your application environments using the Phase service. This SDK let's you create, retrieve, update, and delete secrets, with end-to-end encryption with just a few lines of code.
The Phase Secrets SDK provides a Go package for managing secrets in your application environments using the Phase service. This SDK allows you to create, retrieve, update, and delete secrets with end-to-end encryption using just a few lines of code.

## Features:
## Features

- End-to-end encrypting secret CRUD
- Cross and local env secret referencing
- Built in handling of rate limiting
- End-to-end encrypted secret CRUD operations
- Cross-environment and local environment secret referencing
- Bulk secret operations

### Secret referencing syntax:
### Secret Referencing Syntax

| Reference syntax | Environment | Path | Secret Key Being Referenced | Description |
| --------------------------------- | ---------------- | --------------------------------- | --------------------------- | ------------------------------------------------------------------ |
| `${KEY}` | same environment | `/` | KEY | Local reference in the same environment and path root (/). |
| `${staging.DEBUG}` | `dev` | `/` (root of staging environment) | DEBUG | Cross-environment reference to a secret at the root (/). |
| `${prod./frontend/SECRET_KEY}` | `prod` | `/frontend/` | SECRET_KEY | Cross-environment reference to a secret in a specific path. |
| `${/backend/payments/STRIPE_KEY}` | same environment | `/backend/payments/` | STRIPE_KEY | Local reference with a specified path within the same environment. |
| Reference Syntax | Environment | Path | Secret Key | Description |
|-----------------------------------|------------------|-----------------------------------|------------------------|-------------------------------------------------------------|
| `${KEY}` | Same environment | `/` | KEY | Local reference in the same environment and root path (/). |
| `${staging.DEBUG}` | `staging` | `/` (root of staging environment) | DEBUG | Cross-environment reference to a secret at the root (/). |
| `${prod./frontend/SECRET_KEY}` | `prod` | `/frontend/` | SECRET_KEY | Cross-environment reference to a secret in a specific path. |
| `${/backend/payments/STRIPE_KEY}` | Same environment | `/backend/payments/` | STRIPE_KEY | Local reference with a specified path. |

## Installation

This SDK uses the `sodium` package to perform cryptographic operations, on most system you will need to install the `libsodium` library as a system dependency. Here's how you can install `libsodium` or its development packages on different platforms, including macOS, Ubuntu, Debian, Arch Linux, Alpine Linux, and Windows.

### macOS
This SDK uses the `sodium` package for cryptographic operations. On most systems, you'll need to install the `libsodium` library as a system dependency.

#### macOS
```sh
brew install libsodium
```

## Fedora

#### Fedora
```sh
sudo dnf install libsodium-devel
```

### Ubuntu and Debian

#### Ubuntu and Debian
```sh
sudo apt-get update && sudo apt-get install libsodium-dev
```

### Arch Linux

#### Arch Linux
```sh
sudo pacman -Syu libsodium
```

### Alpine Linux

#### Alpine Linux
```sh
sudo apk add libsodium-dev
```

### Windows
#### Windows
For Windows, download pre-built binaries from the [libsodium GitHub releases page](https://github.com/jedisct1/libsodium/releases). Choose the appropriate version for your system architecture and follow the included instructions.

On Windows, the process is a bit different due to the variety of development environments. However, you can download pre-built binaries from the official [libsodium GitHub releases page](https://github.com/jedisct1/libsodium/releases). Choose the appropriate version for your system architecture (e.g., Win32 or Win64), download it, and follow the instructions included to integrate `libsodium` with your development environment. For development with Visual Studio, you'll typically include the header files and link against the `libsodium.lib` or `libsodium.dll` file.

Expand All @@ -66,23 +64,23 @@ If you're using a package manager like `vcpkg` or `chocolatey`, you can also fin
choco install libsodium
```

Remember, after installing the library, you might need to configure your project or environment variables to locate the `libsodium` libraries correctly, especially on Windows.
### Installing the SDK

Next, start using the Phase SDK in your Go project, install it using `go get`:
To start using the Phase SDK in your Go project, install it using `go get`:

```bash
go get github.com/phasehq/golang-sdk/phase
```

Make sure to import the SDK in your Go files:
Import the SDK in your Go files:

```go
import "github.com/phasehq/golang-sdk/phase"
```

## Configuration

Before you can interact with the Phase service, you need to initialize the SDK with your service token and the host information.
Initialize the SDK with your service token and host information:

```go
package main
Expand All @@ -94,8 +92,8 @@ import (

func main() {
serviceToken := "pss_service:v1:....."
host := "https://console.phase.dev" // Change this for a self hosted instance of Phase
debug := false
host := "https://console.phase.dev" // Change this for a self-hosted instance of Phase
debug := false // For logging verbosity, disable in production

phaseClient := phase.Init(serviceToken, host, debug)
if phaseClient == nil {
Expand All @@ -104,17 +102,19 @@ func main() {
}
```

## Creating a Secret
## Usage

### Creating a Secret

To create new secrets, define key-value pairs, specify the environment and application name, and optionally set paths for each key.
Define key-value pairs, specify the environment and application (using either name or ID), and optionally set paths for each key:

```go
opts := phase.CreateSecretsOptions{
KeyValuePairs: []map[string]string{
{"API_KEY": "api_secret"},
},
EnvName: "Production",
AppName: "MyApp",
AppName: "MyApp", // Or use AppID: "app-id-here"
SecretPath: map[string]string{"API_KEY": "/api/keys"}, // Optional, default path: /
}

Expand All @@ -124,14 +124,14 @@ if err != nil {
}
```

## Retrieving a Secret
### Retrieving a Secret

To retrieve a secret, provide the environment name, application name, key to find, and optionally a tag and path.
Provide the environment name, application (name or ID), key to find, and optionally a tag and path:

```go
getOpts := phase.GetSecretOptions{
EnvName: "Production",
AppName: "MyApp",
AppName: "MyApp", // Or use AppID: "app-id-here"
KeyToFind: "API_KEY",
}

Expand All @@ -143,14 +143,14 @@ if err != nil {
}
```

## Updating a Secret
### Updating a Secret

To update an existing secret, provide the new value along with the environment name, application name, key, and optionally the path.
Provide the new value along with the environment name, application (name or ID), key, and optionally the path:

```go
updateOpts := phase.SecretUpdateOptions{
EnvName: "Production",
AppName: "MyApp",
AppName: "MyApp", // Or use AppID: "app-id-here"
Key: "API_KEY",
Value: "my_updated_api_secret",
SecretPath: "/api/keys", // Optional, default path: /
Expand All @@ -162,14 +162,14 @@ if err != nil {
}
```

## Deleting a Secret
### Deleting a Secret

To delete a secret, specify the environment name, application name, key to delete, and optionally the path.
Specify the environment name, application (name or ID), key to delete, and optionally the path:

```go
deleteOpts := phase.DeleteSecretOptions{
EnvName: "Production",
AppName: "MyApp",
AppName: "MyApp", // Or use AppID: "app-id-here"
KeyToDelete: "API_KEY",
SecretPath: "/api/keys", // Optional, default path: /
}
Expand All @@ -180,6 +180,7 @@ if err != nil {
}
```

For more information and advanced usage, refer to the [Phase Docs](https://docs.phase.dev/sdks/go).
For more information on advanced usage, including detailed API references and best practices, please refer to the [Phase Docs](https://docs.phase.dev/sdks/go).


---
If you encounter any issues or have questions, please file an issue on the [GitHub repository](https://github.com/phasehq/golang-sdk) or contact our support team over [Slack](https://slack.phase.dev).
16 changes: 14 additions & 2 deletions phase/misc/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

const (
Version = "1.0"
Version = "1.0.1"
PhVersion = "v1"
PhaseCloudAPIHost = "https://console.phase.dev"
)
Expand All @@ -21,7 +21,7 @@ var (
PssUserPattern = regexp.MustCompile(`^pss_user:v(\d+):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64})$`)
PssServicePattern = regexp.MustCompile(`^pss_service:v(\d+):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64}):([a-fA-F0-9]{64})$`)

//CrossEnvPattern = regexp.MustCompile(`\$\{(.+?)\.(.+?)\}`)
// CrossEnvPattern = regexp.MustCompile(`\$\{(.+?)\.(.+?)\}`)
// LocalRefPattern = regexp.MustCompile(`\$\{([^.]+?)\}`)

// Regex to identify secret references
Expand Down Expand Up @@ -58,3 +58,15 @@ type AppKeyResponse struct {
WrappedKeyShare string `json:"wrapped_key_share"`
Apps []App `json:"apps"`
}

type GetContextOptions struct {
AppName string
AppID string
EnvName string
}

type FindEnvironmentKeyOptions struct {
EnvName string
AppName string
AppID string
}
38 changes: 18 additions & 20 deletions phase/misc/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"strings"
)

// phaseGetContext finds the matching application and environment, returning their IDs and the public key.
func PhaseGetContext(userData AppKeyResponse, appName, envName string) (string, string, string, error) {
// PhaseGetContext finds the matching application and environment, returning their IDs and the public key.
func PhaseGetContext(userData AppKeyResponse, opts GetContextOptions) (string, string, string, error) {
for _, app := range userData.Apps {
if app.Name == appName {
if (opts.AppID != "" && app.ID == opts.AppID) || (opts.AppName != "" && app.Name == opts.AppName) {
for _, envKey := range app.EnvironmentKeys {
if envKey.Environment.Name == envName {
if envKey.Environment.Name == opts.EnvName {
return app.ID, envKey.Environment.ID, envKey.IdentityKey, nil
}
}
Expand All @@ -20,23 +20,21 @@ func PhaseGetContext(userData AppKeyResponse, appName, envName string) (string,
}

// FindEnvironmentKey searches for an environment key with case-insensitive and partial matching.
func FindEnvironmentKey(userData AppKeyResponse, envName, appName string) (*EnvironmentKey, error) {
// Convert envName and appName to lowercase for case-insensitive comparison
lcEnvName := strings.ToLower(envName)
lcAppName := strings.ToLower(appName)
func FindEnvironmentKey(userData AppKeyResponse, opts FindEnvironmentKeyOptions) (*EnvironmentKey, error) {
lcEnvName := strings.ToLower(opts.EnvName)
lcAppName := strings.ToLower(opts.AppName)

for _, app := range userData.Apps {
// Support partial and case-insensitive matching for appName
if appName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName) {
for _, envKey := range app.EnvironmentKeys {
// Support partial and case-insensitive matching for envName
if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) {
return &envKey, nil
}
}
}
}
return nil, fmt.Errorf("environment key not found for app '%s' and environment '%s'", appName, envName)
for _, app := range userData.Apps {
if (opts.AppID != "" && app.ID == opts.AppID) ||
(opts.AppName != "" && (opts.AppName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName))) {
for _, envKey := range app.EnvironmentKeys {
if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) {
return &envKey, nil
}
}
}
}
return nil, fmt.Errorf("environment key not found for app '%s' (ID: %s) and environment '%s'", opts.AppName, opts.AppID, opts.EnvName)
}

// normalizeTag replaces underscores with spaces and converts the string to lower case.
Expand Down
40 changes: 25 additions & 15 deletions phase/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,37 @@ func ConstructHTTPHeaders(appToken string) http.Header {
return headers
}

var customUserAgent string

func SetUserAgent(ua string) {
customUserAgent = ua
}

func GetUserAgent() string {
details := []string{}
if customUserAgent != "" {
return customUserAgent
}

cliVersion := "phase-golang-sdk/" + misc.Version
details = append(details, cliVersion)
details := []string{}

osType := runtime.GOOS
architecture := runtime.GOARCH
details = append(details, fmt.Sprintf("%s %s", osType, architecture))
cliVersion := "phase-golang-sdk/" + misc.Version
details = append(details, cliVersion)

currentUser, err := user.Current()
if err == nil {
hostname, err := os.Hostname()
if err == nil {
userHostString := fmt.Sprintf("%s@%s", currentUser.Username, hostname)
details = append(details, userHostString)
}
}
osType := runtime.GOOS
architecture := runtime.GOARCH
details = append(details, fmt.Sprintf("%s %s", osType, architecture))

currentUser, err := user.Current()
if err == nil {
hostname, err := os.Hostname()
if err == nil {
userHostString := fmt.Sprintf("%s@%s", currentUser.Username, hostname)
details = append(details, userHostString)
}
}

// Return only the concatenated string without "User-Agent:" prefix
return strings.Join(details, " ")
return strings.Join(details, " ")
}

func createHTTPClient() *http.Client {
Expand Down
Loading