Skip to content

Commit

Permalink
Add Azure HTTP auth plugin
Browse files Browse the repository at this point in the history
This commit adds an HTTP auth plugin that fetches bearer access tokens using managed identities for Azure resources. This plugin will complement the existing AWS and GCP auth plugins.

Signed-off-by: David Lu <david.scowluga@gmail.com>
  • Loading branch information
Scowluga committed Nov 1, 2021
1 parent c23bf8d commit 3129a4b
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 6 deletions.
8 changes: 8 additions & 0 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,14 @@ bundles:
max_delay_seconds: 120
```

#### Azure Metadata Token

OPA will authenticate with Azure managed identities ...

##### Example

...

#### Custom Plugin

If none of the existing credential options work for a service, OPA can authenticate using a custom plugin, enabling support for any authentication scheme.
Expand Down
138 changes: 138 additions & 0 deletions plugins/rest/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package rest

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)

var (
azureIMDSEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
timeout = time.Duration(5) * time.Second
)

// azureManagedIdentitiesToken holds a token for managed identities for Azure resources
type azureManagedIdentitiesToken struct {
AccessToken string `json:"access_token"`
ExpiresIn string `json:"expires_in"`
ExpiresOn string `json:"expires_on"`
NotBefore string `json:"not_before"`
Resource string `json:"resource"`
TokenType string `json:"token_type"`
}

// azureManagedIdentitiesError represents an error fetching an azureManagedIdentitiesToken
type azureManagedIdentitiesError struct {
err string `json:"error"`
description string `json:"error_description"`
endpoint string
statusCode int
}

func (e *azureManagedIdentitiesError) Error() string {
return fmt.Sprintf("%v %s retrieving azure token from %s: %s", e.statusCode, e.err, e.endpoint, e.description)
}

// azureManagedIdentitiesAuthPlugin uses an azureManagedIdentitiesToken.AccessToken for bearer authorization
type azureManagedIdentitiesAuthPlugin struct {
Endpoint string `json:"endpoint"`
APIVersion string `json:"api_version"`
Resource string `json:"resource"`
ObjectId string `json:"object_id"`
ClientId string `json:"client_id"`
MiResId string `json:"mi_res_id"`
}

func (ap *azureManagedIdentitiesAuthPlugin) NewClient(c Config) (*http.Client, error) {
if ap.APIVersion == "" {
return nil, errors.New("API version is required when the azure managed identities plugin is enabled")
}

if ap.Resource == "" {
return nil, errors.New("resource URI is required when the azure managed identities plugin is enabled")
}

if ap.Endpoint == "" {
ap.Endpoint = azureIMDSEndpoint
}

t, err := DefaultTLSConfig(c)
if err != nil {
return nil, err
}

return DefaultRoundTripperClient(t, *c.ResponseHeaderTimeoutSeconds), nil
}

func (ap *azureManagedIdentitiesAuthPlugin) Prepare(req *http.Request) error {
token, err := azureManagedIdentitiesTokenRequest(
ap.Endpoint, ap.APIVersion, ap.Resource,
ap.ObjectId, ap.ClientId, ap.MiResId,
)
if err != nil {
return err
}

req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token.AccessToken))
return nil
}

// azureManagedIdentitiesTokenRequest fetches an azureManagedIdentitiesToken
func azureManagedIdentitiesTokenRequest(
endpoint, apiVersion, resource, objectId, clientId, miResId string,
) (azureManagedIdentitiesToken, error) {
e := fmt.Sprintf("%s?api-version=%s&resource=%s", endpoint, apiVersion, resource)

if objectId != "" {
e += fmt.Sprintf("&object_id=%s", objectId)
}

if clientId != "" {
e += fmt.Sprintf("&client_id=%s", clientId)
}

if miResId != "" {
e += fmt.Sprintf("&mi_res_id=%s", miResId)
}

request, err := http.NewRequest("GET", e, nil)
if err != nil {
return azureManagedIdentitiesToken{}, err
}
request.Header.Add("Metadata", "true")

httpClient := http.Client{Timeout: timeout}
response, err := httpClient.Do(request)
if err != nil {
return azureManagedIdentitiesToken{}, err
}

data, err := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return azureManagedIdentitiesToken{}, err
}

if s := response.StatusCode; s != http.StatusOK {
var azureError azureManagedIdentitiesError
err = json.Unmarshal(data, &azureError)
if err != nil {
return azureManagedIdentitiesToken{}, err
}

azureError.endpoint = e
azureError.statusCode = s
return azureManagedIdentitiesToken{}, &azureError
}

var accessToken azureManagedIdentitiesToken
err = json.Unmarshal(data, &accessToken)
if err != nil {
return azureManagedIdentitiesToken{}, err
}

return accessToken, nil
}
1 change: 1 addition & 0 deletions plugins/rest/azure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package rest
13 changes: 7 additions & 6 deletions plugins/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ type Config struct {
ResponseHeaderTimeoutSeconds *int64 `json:"response_header_timeout_seconds,omitempty"`
TLS *serverTLSConfig `json:"tls,omitempty"`
Credentials struct {
Bearer *bearerAuthPlugin `json:"bearer,omitempty"`
OAuth2 *oauth2ClientCredentialsAuthPlugin `json:"oauth2,omitempty"`
ClientTLS *clientTLSAuthPlugin `json:"client_tls,omitempty"`
S3Signing *awsSigningAuthPlugin `json:"s3_signing,omitempty"`
GCPMetadata *gcpMetadataAuthPlugin `json:"gcp_metadata,omitempty"`
Plugin *string `json:"plugin,omitempty"`
Bearer *bearerAuthPlugin `json:"bearer,omitempty"`
OAuth2 *oauth2ClientCredentialsAuthPlugin `json:"oauth2,omitempty"`
ClientTLS *clientTLSAuthPlugin `json:"client_tls,omitempty"`
S3Signing *awsSigningAuthPlugin `json:"s3_signing,omitempty"`
GCPMetadata *gcpMetadataAuthPlugin `json:"gcp_metadata,omitempty"`
AzureMetadata *azureManagedIdentitiesAuthPlugin `json:"azure_metadata,omitempty"`
Plugin *string `json:"plugin,omitempty"`
} `json:"credentials"`
keys map[string]*keys.Config
logger logging.Logger
Expand Down

0 comments on commit 3129a4b

Please sign in to comment.