Skip to content

Commit

Permalink
Merge pull request #48 from Infisical/feat/add-oidc-support
Browse files Browse the repository at this point in the history
misc: added support for OIDC auth
  • Loading branch information
sheensantoscapadngan authored Jul 24, 2024
2 parents 0eb1878 + ae6bbee commit 849e0c5
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 67 deletions.
25 changes: 25 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,32 @@ provider "infisical" {

### Optional

- `auth` (Attributes) The configuration values for authentication (see [below for nested schema](#nestedatt--auth))
- `client_id` (String, Sensitive) Machine identity client ID. Used to fetch/modify secrets for a given project
- `client_secret` (String, Sensitive) Machine identity client secret. Used to fetch/modify secrets for a given project
- `host` (String) Used to point the client to fetch secrets from your self hosted instance of Infisical. If not host is provided, https://app.infisical.com is the default host.
- `service_token` (String, Sensitive) (DEPRECATED, USE MACHINE IDENTITY), Used to fetch/modify secrets for a given project

<a id="nestedatt--auth"></a>
### Nested Schema for `auth`

Optional:

- `oidc` (Attributes) The configuration values for OIDC Auth (see [below for nested schema](#nestedatt--auth--oidc))
- `universal` (Attributes) The configuration values for Universal Auth (see [below for nested schema](#nestedatt--auth--universal))

<a id="nestedatt--auth--oidc"></a>
### Nested Schema for `auth.oidc`

Optional:

- `identity_id` (String, Sensitive) Machine identity ID. Used to fetch/modify secrets for a given project


<a id="nestedatt--auth--universal"></a>
### Nested Schema for `auth.universal`

Optional:

- `client_id` (String, Sensitive) Machine identity client ID. Used to fetch/modify secrets for a given project
- `client_secret` (String, Sensitive) Machine identity client secret. Used to fetch/modify secrets for a given project
41 changes: 25 additions & 16 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,26 @@ type AuthStrategyType string
var AuthStrategy = struct {
SERVICE_TOKEN AuthStrategyType
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
OIDC_MACHINE_IDENTITY AuthStrategyType
}{
SERVICE_TOKEN: "SERVICE_TOKEN",
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
OIDC_MACHINE_IDENTITY: "OIDC_MACHINE_IDENTITY",
}

type Config struct {
HostURL string

AuthStrategy AuthStrategyType
AuthStrategy AuthStrategyType
IsMachineIdentityAuth bool

// Service Token Auth
ServiceToken string

// Universal Machine Identity Auth
ClientId string
ClientSecret string
IdentityId string

EnvSlug string
SecretsPath string
Expand All @@ -43,32 +47,37 @@ func NewClient(cnf Config) (*Client, error) {
cnf.HttpClient.SetBaseURL(cnf.HostURL)
}

// Add more auth strategies here later
var usingServiceToken = cnf.ServiceToken != ""
var usingUniversalAuth = cnf.ClientId != "" && cnf.ClientSecret != ""

selectedAuthStrategy := cnf.AuthStrategy
if cnf.ClientId != "" && cnf.ClientSecret != "" && selectedAuthStrategy == "" {
selectedAuthStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
}

// Check if the user got multiple configured authentication methods, or none set at all.
if usingServiceToken && usingUniversalAuth {
if usingServiceToken && selectedAuthStrategy != "" {
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")
} else if !usingServiceToken && selectedAuthStrategy == "" {
return nil, fmt.Errorf("you must configure an authentication method such as service tokens or Universal Auth before making calls")
}

if usingUniversalAuth {
token, err := Client{cnf}.UniversalMachineIdentityAuth()
if usingServiceToken {
cnf.HttpClient.SetAuthToken(cnf.ServiceToken)
cnf.AuthStrategy = AuthStrategy.SERVICE_TOKEN
} else {
authStrategies := map[AuthStrategyType]func() (token string, e error){
AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: Client{cnf}.UniversalMachineIdentityAuth,
AuthStrategy.OIDC_MACHINE_IDENTITY: Client{cnf}.OidcMachineIdentityAuth,
}

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

cnf.AuthStrategy = selectedAuthStrategy
cnf.IsMachineIdentityAuth = true
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.
Expand Down
10 changes: 9 additions & 1 deletion internal/client/constants.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
package infisicalclient

const USER_AGENT = "terraform"
const (
USER_AGENT = "terraform"
INFISICAL_MACHINE_IDENTITY_ID_NAME = "INFISICAL_MACHINE_IDENTITY_ID"
INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME = "INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET"
INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME = "INFISICAL_UNIVERSAL_AUTH_CLIENT_ID"
INFISICAL_SERVICE_TOKEN_NAME = "INFISICAL_SERVICE_TOKEN"
INFISICAL_HOST_NAME = "INFISICAL_HOST"
INFISICAL_AUTH_JWT_NAME = "INFISICAL_AUTH_JWT"
)
36 changes: 34 additions & 2 deletions internal/client/login.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package infisicalclient

import "fmt"
import (
"fmt"
"os"
)

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
var loginResponse MachineIdentityAuthResponse

res, err := client.Config.HttpClient.R().SetResult(&loginResponse).SetHeader("User-Agent", USER_AGENT).SetBody(map[string]string{
"clientId": client.Config.ClientId,
Expand Down Expand Up @@ -43,3 +46,32 @@ func (client Client) GetServiceTokenDetailsV2() (GetServiceTokenDetailsResponse,

return tokenDetailsResponse, nil
}

func (client Client) OidcMachineIdentityAuth() (string, error) {
authJwt := os.Getenv(INFISICAL_AUTH_JWT_NAME)

if client.Config.IdentityId == "" {
return "", fmt.Errorf("you must set the identity ID for the client before making calls")
}

if authJwt == "" {
return "", fmt.Errorf("%s is not present in the environment", INFISICAL_AUTH_JWT_NAME)
}

var loginResponse MachineIdentityAuthResponse

res, err := client.Config.HttpClient.R().SetResult(&loginResponse).SetHeader("User-Agent", USER_AGENT).SetBody(map[string]string{
"identityId": client.Config.IdentityId,
"jwt": authJwt,
}).Post("api/v1/auth/oidc-auth/login")

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

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

return loginResponse.AccessToken, nil
}
2 changes: 1 addition & 1 deletion internal/client/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ type GetServiceTokenDetailsResponse struct {
V int `json:"__v"`
}

type UniversalMachineIdentityAuthResponse struct {
type MachineIdentityAuthResponse struct {
AccessToken string `json:"accessToken"`
ExpiresIn int `json:"expiresIn"`
AccessTokenMaxTTL int `json:"accessTokenMaxTTL"`
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/datasource/projects_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (d *ProjectsDataSource) Configure(ctx context.Context, req datasource.Confi

func (d *ProjectsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {

if d.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !d.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to create project",
"Only Machine Identity authentication is supported for this operation",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/datasource/secret_folder_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (d *SecretFoldersDataSource) Configure(ctx context.Context, req datasource.

func (d *SecretFoldersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {

if d.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !d.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to create infisical secrets folder",
"Only Machine Identity authentication is supported for this operation",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/datasource/secret_tag_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (d *SecretTagsDataSource) Configure(ctx context.Context, req datasource.Con

func (d *SecretTagsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {

if d.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !d.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to create secretTag tag",
"Only Machine Identity authentication is supported for this operation",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/datasource/secrets_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (d *SecretsDataSource) Read(ctx context.Context, req datasource.ReadRequest
for _, secret := range plainTextSecrets {
data.Secrets[secret.Key] = InfisicalSecretDetails{Value: types.StringValue(secret.Value), Comment: types.StringValue(secret.Comment), SecretType: types.StringValue(secret.Type)}
}
} else if d.client.Config.AuthStrategy == infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
} else if d.client.Config.IsMachineIdentityAuth {
secrets, err := d.client.GetRawSecrets(data.FolderPath.ValueString(), data.EnvSlug.ValueString(), data.WorkspaceId.ValueString())
if err != nil {
resp.Diagnostics.AddError(
Expand Down
82 changes: 76 additions & 6 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ type infisicalProviderModel struct {

ClientId types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`

Auth *authModel `tfsdk:"auth"`
}

type authModel struct {
Oidc *oidcAuthModel `tfsdk:"oidc"`
Universal *universalAuthModel `tfsdk:"universal"`
}

type oidcAuthModel struct {
IdentityId types.String `tfsdk:"identity_id"`
}

type universalAuthModel struct {
ClientId types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`
}

// Metadata returns the provider type name.
Expand All @@ -66,7 +82,6 @@ func (p *infisicalProvider) Schema(ctx context.Context, _ provider.SchemaRequest
Sensitive: true,
Description: " (DEPRECATED, USE MACHINE IDENTITY), Used to fetch/modify secrets for a given project",
},

"client_id": schema.StringAttribute{
Optional: true,
Sensitive: true,
Expand All @@ -77,6 +92,39 @@ func (p *infisicalProvider) Schema(ctx context.Context, _ provider.SchemaRequest
Sensitive: true,
Description: "Machine identity client secret. Used to fetch/modify secrets for a given project",
},
"auth": schema.SingleNestedAttribute{
Optional: true,
Description: "The configuration values for authentication",
Attributes: map[string]schema.Attribute{
"universal": schema.SingleNestedAttribute{
Optional: true,
Description: "The configuration values for Universal Auth",
Attributes: map[string]schema.Attribute{
"client_id": schema.StringAttribute{
Optional: true,
Sensitive: true,
Description: "Machine identity client ID. Used to fetch/modify secrets for a given project",
},
"client_secret": schema.StringAttribute{
Optional: true,
Sensitive: true,
Description: "Machine identity client secret. Used to fetch/modify secrets for a given project",
},
},
},
"oidc": schema.SingleNestedAttribute{
Optional: true,
Description: "The configuration values for OIDC Auth",
Attributes: map[string]schema.Attribute{
"identity_id": schema.StringAttribute{
Optional: true,
Sensitive: true,
Description: "Machine identity ID. Used to fetch/modify secrets for a given project",
},
},
},
},
},
},
}
}
Expand All @@ -96,14 +144,15 @@ func (p *infisicalProvider) Configure(ctx context.Context, req provider.Configur
resp.Diagnostics.AddError("No authentication credentials provided", "You must define service_token field of the provider")
}

host := os.Getenv("INFISICAL_HOST")
host := os.Getenv(infisical.INFISICAL_HOST_NAME)

// Service Token
serviceToken := os.Getenv("INFISICAL_SERVICE_TOKEN")
serviceToken := os.Getenv(infisical.INFISICAL_SERVICE_TOKEN_NAME)

// Machine Identity
clientId := os.Getenv("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID")
clientSecret := os.Getenv("INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET")
clientId := os.Getenv(infisical.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
clientSecret := os.Getenv(infisical.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME)
identityId := os.Getenv(infisical.INFISICAL_MACHINE_IDENTITY_ID_NAME)

if !config.Host.IsNull() {
host = config.Host.ValueString()
Expand All @@ -130,7 +179,28 @@ func (p *infisicalProvider) Configure(ctx context.Context, req provider.Configur
return
}

client, err := infisical.NewClient(infisical.Config{HostURL: host, ServiceToken: serviceToken, ClientId: clientId, ClientSecret: clientSecret})
var authStrategy infisical.AuthStrategyType

if config.Auth != nil {
if config.Auth.Oidc != nil {
authStrategy = infisical.AuthStrategy.OIDC_MACHINE_IDENTITY
if !config.Auth.Oidc.IdentityId.IsNull() {
identityId = config.Auth.Oidc.IdentityId.ValueString()
}
}

if config.Auth.Universal != nil {
authStrategy = infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
if !config.Auth.Universal.ClientId.IsNull() {
clientId = config.Auth.Universal.ClientId.ValueString()
}
if !config.Auth.Universal.ClientSecret.IsNull() {
clientSecret = config.Auth.Universal.ClientSecret.ValueString()
}
}
}

client, err := infisical.NewClient(infisical.Config{HostURL: host, AuthStrategy: authStrategy, ServiceToken: serviceToken, ClientId: clientId, ClientSecret: clientSecret, IdentityId: identityId})

if err != nil {
resp.Diagnostics.AddError(
Expand Down
8 changes: 4 additions & 4 deletions internal/provider/resource/project_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (r *projectEnvironmentResource) Configure(_ context.Context, req resource.C

// Create creates the resource and sets the initial Terraform state.
func (r *projectEnvironmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
if r.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !r.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to create project environment",
"Only Machine Identity authentication is supported for this operation",
Expand Down Expand Up @@ -127,7 +127,7 @@ func (r *projectEnvironmentResource) Create(ctx context.Context, req resource.Cr
// Delete deletes the resource and removes the Terraform state on success.
func (r *projectEnvironmentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {

if r.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !r.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to delete project environment",
"Only Machine Identity authentication is supported for this operation",
Expand Down Expand Up @@ -159,7 +159,7 @@ func (r *projectEnvironmentResource) Delete(ctx context.Context, req resource.De

// Read refreshes the Terraform state with the latest data.
func (r *projectEnvironmentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
if r.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !r.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to read project environment",
"Only Machine Identity authentication is supported for this operation",
Expand Down Expand Up @@ -205,7 +205,7 @@ func (r *projectEnvironmentResource) Read(ctx context.Context, req resource.Read

// Update updates the resource and sets the updated Terraform state on success.
func (r *projectEnvironmentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
if r.client.Config.AuthStrategy != infisical.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY {
if !r.client.Config.IsMachineIdentityAuth {
resp.Diagnostics.AddError(
"Unable to update project environment",
"Only Machine Identity authentication is supported for this operation",
Expand Down
Loading

0 comments on commit 849e0c5

Please sign in to comment.