Skip to content

Commit

Permalink
Merge pull request #29 from Infisical/daniel/mi-support
Browse files Browse the repository at this point in the history
(Feat): Machine Identity Auth support
  • Loading branch information
DanielHougaard authored Jan 12, 2024
2 parents e22570c + d05c254 commit c5464da
Show file tree
Hide file tree
Showing 8 changed files with 762 additions and 312 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"[go]": {
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "golang.go"
}
}
134 changes: 134 additions & 0 deletions client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ import (

const USER_AGENT = "terraform"

func (client Client) UniversalMachineIdentityAuth() (string, error) {
if client.Config.ClientId == "" || client.Config.ClientSecret == "" {
return "", fmt.Errorf("you must set the client secret and client ID for the client before making calls")
}

var loginResponse UniversalMachineIdentityAuthResponse

res, err := client.Config.HttpClient.R().SetResult(&loginResponse).SetHeader("User-Agent", USER_AGENT).SetBody(map[string]string{
"clientId": client.Config.ClientId,
"clientSecret": client.Config.ClientSecret,
}).Post("api/v1/auth/universal-auth/login")

if err != nil {
return "", fmt.Errorf("UniversalMachineIdentityAuth: Unable to complete api request [err=%s]", err)
}

if res.IsError() {
return "", fmt.Errorf("UniversalMachineIdentityAuth: Unsuccessful response: [response=%s]", res)
}

return loginResponse.AccessToken, nil
}

func (client Client) CallGetServiceTokenDetailsV2() (GetServiceTokenDetailsResponse, error) {
var tokenDetailsResponse GetServiceTokenDetailsResponse
response, err := client.Config.HttpClient.
Expand Down Expand Up @@ -93,6 +116,7 @@ func (client Client) CallDeleteSecretsV3(request DeleteSecretV3Request) error {
}

func (client Client) CallUpdateSecretsV3(request UpdateSecretByNameV3Request) error {

var secretsResponse GetEncryptedSecretsV3Response
response, err := client.Config.HttpClient.
R().
Expand Down Expand Up @@ -134,3 +158,113 @@ func (client Client) CallGetSingleSecretByNameV3(request GetSingleSecretByNameV3

return secretsResponse, nil
}

func (client Client) CallGetSecretsRawV3(request GetRawSecretsV3Request) (GetRawSecretsV3Response, error) {
var secretsResponse GetRawSecretsV3Response

httpRequest := client.Config.HttpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetQueryParam("environment", request.Environment).
SetQueryParam("workspaceId", request.WorkspaceId)

if request.SecretPath != "" {
httpRequest.SetQueryParam("secretPath", request.SecretPath)
}

response, err := httpRequest.Get("api/v3/secrets/raw")

if err != nil {
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetSecretsRawV3: Unable to complete api request [err=%s]", err)
}

if response.IsError() {
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetSecretsRawV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%v]", response.RawResponse)
}

return secretsResponse, nil
}

func (client Client) CallCreateRawSecretsV3(request CreateRawSecretV3Request) error {
var secretsResponse EncryptedSecretV3
response, err := client.Config.HttpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("api/v3/secrets/raw/%s", request.SecretKey))

if err != nil {
return fmt.Errorf("CallCreateRawSecretsV3: Unable to complete api request [err=%s]", err)
}

if response.IsError() {
return fmt.Errorf("CallCreateRawSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}

return nil
}

func (client Client) CallDeleteRawSecretV3(request DeleteRawSecretV3Request) error {
var secretsResponse GetRawSecretsV3Response
response, err := client.Config.HttpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Delete(fmt.Sprintf("api/v3/secrets/raw/%s", request.SecretName))

if err != nil {
return fmt.Errorf("CallDeleteRawSecretV3: Unable to complete api request [err=%s]", err)
}

if response.IsError() {
return fmt.Errorf("CallDeleteRawSecretV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}

return nil
}

func (client Client) CallUpdateRawSecretV3(request UpdateRawSecretByNameV3Request) error {
var secretsResponse GetRawSecretsV3Response
response, err := client.Config.HttpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Patch(fmt.Sprintf("api/v3/secrets/raw/%s", request.SecretName))

if err != nil {
return fmt.Errorf("CallUpdateRawSecretV3: Unable to complete api request [err=%s]", err)
}

if response.IsError() {
return fmt.Errorf("CallUpdateRawSecretV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}

return nil
}

func (client Client) CallGetSingleRawSecretByNameV3(request GetSingleSecretByNameV3Request) (GetSingleRawSecretByNameSecretResponse, error) {
var secretsResponse GetSingleRawSecretByNameSecretResponse
response, err := client.Config.HttpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetQueryParam("workspaceId", request.WorkspaceId).
SetQueryParam("environment", request.Environment).
SetQueryParam("type", request.Type).
SetQueryParam("secretPath", request.SecretPath).
Get(fmt.Sprintf("api/v3/secrets/raw/%s", request.SecretName))

if err != nil {
return GetSingleRawSecretByNameSecretResponse{}, fmt.Errorf("CallGetSingleRawSecretByNameV3: Unable to complete api request [err=%s]", err)
}

if response.IsError() {
return GetSingleRawSecretByNameSecretResponse{}, fmt.Errorf("CallGetSingleRawSecretByNameV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}

return secretsResponse, nil
}
55 changes: 48 additions & 7 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,31 @@ type Client struct {
Config Config
}

type AuthStrategyType string

var AuthStrategy = struct {
SERVICE_TOKEN AuthStrategyType
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
}{
SERVICE_TOKEN: "SERVICE_TOKEN",
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
}

type Config struct {
HostURL string
HostURL string

AuthStrategy AuthStrategyType

// Service Token Auth
ServiceToken string
EnvSlug string
SecretsPath string
HttpClient *resty.Client // By default a client will be created

// Universal Machine Identity Auth
ClientId string
ClientSecret string

EnvSlug string
SecretsPath string
HttpClient *resty.Client // By default a client will be created
}

func NewClient(cnf Config) (*Client, error) {
Expand All @@ -24,14 +43,36 @@ func NewClient(cnf Config) (*Client, error) {
cnf.HttpClient.SetBaseURL(cnf.HostURL)
}

if cnf.ServiceToken == "" {
return nil, fmt.Errorf("you must set the service token for the client before making calls")
// Add more auth strategies here later
var usingServiceToken = cnf.ServiceToken != ""
var usingUniversalAuth = cnf.ClientId != "" && cnf.ClientSecret != ""

// Check if the user got multiple configured authentication methods, or none set at all.
if usingServiceToken && usingUniversalAuth {
return nil, fmt.Errorf("you have configured multiple authentication methods, please only use one")
} else if !usingServiceToken && !usingUniversalAuth {
return nil, fmt.Errorf("you must configure a authentication method such as service tokens or Universal Auth before making calls")
}

if cnf.ServiceToken != "" {
if usingUniversalAuth {
token, err := Client{cnf}.UniversalMachineIdentityAuth()

if err != nil {
return nil, fmt.Errorf("unable to authenticate with universal machine identity [err=%s]", err)
}

cnf.HttpClient.SetAuthToken(token)
cnf.AuthStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
} else if usingServiceToken {
cnf.HttpClient.SetAuthToken(cnf.ServiceToken)
cnf.AuthStrategy = AuthStrategy.SERVICE_TOKEN
} else {
// If no auth strategy is set, then we should return an error
return nil, fmt.Errorf("you must configure a authentication method such as service tokens or Universal Auth before making calls")
}

// These two if statements were a part of an older migration.
// And when people upgraded to the newer version, we needed a way to indicate that the EnvSlug and SecretsPath are no longer defined on a provider-level.
if cnf.EnvSlug != "" {
return nil, fmt.Errorf("you must set the environment before making calls")
}
Expand Down
60 changes: 59 additions & 1 deletion client/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ type GetServiceTokenDetailsResponse struct {
V int `json:"__v"`
}

//
type UniversalMachineIdentityAuthResponse struct {
AccessToken string `json:"accessToken"`
ExpiresIn int `json:"expiresIn"`
AccessTokenMaxTTL int `json:"accessTokenMaxTTL"`
TokenType string `json:"tokenType"`
}

type SingleEnvironmentVariable struct {
Key string `json:"key"`
Expand Down Expand Up @@ -174,3 +179,56 @@ type GetSingleSecretByNameV3Request struct {
type GetSingleSecretByNameSecretResponse struct {
Secret EncryptedSecret `json:"secret"`
}

type GetRawSecretsV3Request struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
SecretPath string `json:"secretPath"`
}

type RawV3Secret struct {
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKey string `json:"secretKey"`
SecretValue string `json:"secretValue"`
SecretComment string `json:"secretComment"`
}

type GetRawSecretsV3Response struct {
Secrets []RawV3Secret `json:"secrets"`
}

type GetSingleRawSecretByNameSecretResponse struct {
Secret RawV3Secret `json:"secret"`
}

// create secrets

Check failure on line 207 in client/model.go

View workflow job for this annotation

GitHub Actions / Build

Comment should end in a period (godot)
type CreateRawSecretV3Request struct {
WorkspaceID string `json:"workspaceId"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKey string `json:"secretKey"`
SecretValue string `json:"secretValue"`
SecretComment string `json:"secretComment"`
SecretPath string `json:"secretPath"`
}

type DeleteRawSecretV3Request struct {
SecretName string `json:"secretName"`
WorkspaceId string `json:"workspaceId"`
Environment string `json:"environment"`
Type string `json:"type"`
SecretPath string `json:"secretPath"`
}

// update secret by name api
type UpdateRawSecretByNameV3Request struct {
SecretName string `json:"secretName"`
WorkspaceID string `json:"workspaceId"`
Environment string `json:"environment"`
Type string `json:"type"`
SecretPath string `json:"secretPath"`
SecretValue string `json:"secretValue"`
}
24 changes: 24 additions & 0 deletions client/secret-operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,27 @@ func (client Client) GetPlainTextSecretsViaServiceToken(secretFolderPath string,

return plainTextSecrets, &serviceTokenDetails, nil
}

func (client Client) GetRawSecrets(secretFolderPath string, envSlug string, workspaceId string) ([]RawV3Secret, error) {
if client.Config.ClientId == "" || client.Config.ClientSecret == "" {
return nil, fmt.Errorf("client ID and client secret must be defined to fetch secrets with machine identity")
}

request := GetRawSecretsV3Request{
Environment: envSlug,
WorkspaceId: workspaceId,
}

if secretFolderPath != "" {
request.SecretPath = secretFolderPath
}

secrets, err := client.CallGetSecretsRawV3(request)

if err != nil {
return nil, err
}

return secrets.Secrets, nil

}
Loading

0 comments on commit c5464da

Please sign in to comment.