Skip to content

Commit 75e2bc3

Browse files
authored
Merge pull request #8 from phasehq/feat--app-id
feat: fetch secrets via Phase application ID
2 parents 4ace8d4 + 5a25197 commit 75e2bc3

File tree

5 files changed

+213
-165
lines changed

5 files changed

+213
-165
lines changed

README.md

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,55 @@
1-
# Phase Secrets Management SDK
1+
# Phase Secrets Management SDK for Go
22

3-
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.
3+
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.
44

5-
## Features:
5+
## Features
66

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

11-
### Secret referencing syntax:
11+
### Secret Referencing Syntax
1212

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

2020
## Installation
2121

2222
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.
2323

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

26+
#### macOS
2627
```sh
2728
brew install libsodium
2829
```
2930

30-
## Fedora
31-
31+
#### Fedora
3232
```sh
3333
sudo dnf install libsodium-devel
3434
```
3535

36-
### Ubuntu and Debian
37-
36+
#### Ubuntu and Debian
3837
```sh
3938
sudo apt-get update && sudo apt-get install libsodium-dev
4039
```
4140

42-
### Arch Linux
43-
41+
#### Arch Linux
4442
```sh
4543
sudo pacman -Syu libsodium
4644
```
4745

48-
### Alpine Linux
49-
46+
#### Alpine Linux
5047
```sh
5148
sudo apk add libsodium-dev
5249
```
5350

54-
### Windows
51+
#### Windows
52+
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.
5553

5654
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.
5755

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

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

71-
Next, start using the Phase SDK in your Go project, install it using `go get`:
69+
To start using the Phase SDK in your Go project, install it using `go get`:
7270

7371
```bash
7472
go get github.com/phasehq/golang-sdk/phase
7573
```
7674

77-
Make sure to import the SDK in your Go files:
75+
Import the SDK in your Go files:
7876

7977
```go
8078
import "github.com/phasehq/golang-sdk/phase"
8179
```
8280

8381
## Configuration
8482

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

8785
```go
8886
package main
@@ -94,8 +92,8 @@ import (
9492

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

10098
phaseClient := phase.Init(serviceToken, host, debug)
10199
if phaseClient == nil {
@@ -104,17 +102,19 @@ func main() {
104102
}
105103
```
106104

107-
## Creating a Secret
105+
## Usage
106+
107+
### Creating a Secret
108108

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

111111
```go
112112
opts := phase.CreateSecretsOptions{
113113
KeyValuePairs: []map[string]string{
114114
{"API_KEY": "api_secret"},
115115
},
116116
EnvName: "Production",
117-
AppName: "MyApp",
117+
AppName: "MyApp", // Or use AppID: "app-id-here"
118118
SecretPath: map[string]string{"API_KEY": "/api/keys"}, // Optional, default path: /
119119
}
120120

@@ -124,14 +124,14 @@ if err != nil {
124124
}
125125
```
126126

127-
## Retrieving a Secret
127+
### Retrieving a Secret
128128

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

131131
```go
132132
getOpts := phase.GetSecretOptions{
133133
EnvName: "Production",
134-
AppName: "MyApp",
134+
AppName: "MyApp", // Or use AppID: "app-id-here"
135135
KeyToFind: "API_KEY",
136136
}
137137

@@ -143,14 +143,14 @@ if err != nil {
143143
}
144144
```
145145

146-
## Updating a Secret
146+
### Updating a Secret
147147

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

150150
```go
151151
updateOpts := phase.SecretUpdateOptions{
152152
EnvName: "Production",
153-
AppName: "MyApp",
153+
AppName: "MyApp", // Or use AppID: "app-id-here"
154154
Key: "API_KEY",
155155
Value: "my_updated_api_secret",
156156
SecretPath: "/api/keys", // Optional, default path: /
@@ -162,14 +162,14 @@ if err != nil {
162162
}
163163
```
164164

165-
## Deleting a Secret
165+
### Deleting a Secret
166166

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

169169
```go
170170
deleteOpts := phase.DeleteSecretOptions{
171171
EnvName: "Production",
172-
AppName: "MyApp",
172+
AppName: "MyApp", // Or use AppID: "app-id-here"
173173
KeyToDelete: "API_KEY",
174174
SecretPath: "/api/keys", // Optional, default path: /
175175
}
@@ -180,6 +180,7 @@ if err != nil {
180180
}
181181
```
182182

183-
For more information and advanced usage, refer to the [Phase Docs](https://docs.phase.dev/sdks/go).
183+
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).
184+
184185

185-
---
186+
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).

phase/misc/const.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
)
66

77
const (
8-
Version = "1.0"
8+
Version = "1.0.1"
99
PhVersion = "v1"
1010
PhaseCloudAPIHost = "https://console.phase.dev"
1111
)
@@ -21,7 +21,7 @@ var (
2121
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})$`)
2222
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})$`)
2323

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

2727
// Regex to identify secret references
@@ -58,3 +58,15 @@ type AppKeyResponse struct {
5858
WrappedKeyShare string `json:"wrapped_key_share"`
5959
Apps []App `json:"apps"`
6060
}
61+
62+
type GetContextOptions struct {
63+
AppName string
64+
AppID string
65+
EnvName string
66+
}
67+
68+
type FindEnvironmentKeyOptions struct {
69+
EnvName string
70+
AppName string
71+
AppID string
72+
}

phase/misc/misc.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"strings"
66
)
77

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

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

28-
for _, app := range userData.Apps {
29-
// Support partial and case-insensitive matching for appName
30-
if appName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName) {
31-
for _, envKey := range app.EnvironmentKeys {
32-
// Support partial and case-insensitive matching for envName
33-
if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) {
34-
return &envKey, nil
35-
}
36-
}
37-
}
38-
}
39-
return nil, fmt.Errorf("environment key not found for app '%s' and environment '%s'", appName, envName)
27+
for _, app := range userData.Apps {
28+
if (opts.AppID != "" && app.ID == opts.AppID) ||
29+
(opts.AppName != "" && (opts.AppName == "" || strings.Contains(strings.ToLower(app.Name), lcAppName))) {
30+
for _, envKey := range app.EnvironmentKeys {
31+
if strings.Contains(strings.ToLower(envKey.Environment.Name), lcEnvName) {
32+
return &envKey, nil
33+
}
34+
}
35+
}
36+
}
37+
return nil, fmt.Errorf("environment key not found for app '%s' (ID: %s) and environment '%s'", opts.AppName, opts.AppID, opts.EnvName)
4038
}
4139

4240
// normalizeTag replaces underscores with spaces and converts the string to lower case.

phase/network/network.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,37 @@ func ConstructHTTPHeaders(appToken string) http.Header {
2424
return headers
2525
}
2626

27+
var customUserAgent string
28+
29+
func SetUserAgent(ua string) {
30+
customUserAgent = ua
31+
}
32+
2733
func GetUserAgent() string {
28-
details := []string{}
34+
if customUserAgent != "" {
35+
return customUserAgent
36+
}
2937

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

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

37-
currentUser, err := user.Current()
38-
if err == nil {
39-
hostname, err := os.Hostname()
40-
if err == nil {
41-
userHostString := fmt.Sprintf("%s@%s", currentUser.Username, hostname)
42-
details = append(details, userHostString)
43-
}
44-
}
43+
osType := runtime.GOOS
44+
architecture := runtime.GOARCH
45+
details = append(details, fmt.Sprintf("%s %s", osType, architecture))
46+
47+
currentUser, err := user.Current()
48+
if err == nil {
49+
hostname, err := os.Hostname()
50+
if err == nil {
51+
userHostString := fmt.Sprintf("%s@%s", currentUser.Username, hostname)
52+
details = append(details, userHostString)
53+
}
54+
}
4555

4656
// Return only the concatenated string without "User-Agent:" prefix
47-
return strings.Join(details, " ")
57+
return strings.Join(details, " ")
4858
}
4959

5060
func createHTTPClient() *http.Client {

0 commit comments

Comments
 (0)