Skip to content

Commit

Permalink
Merge pull request #2012 from prestist/update-azure-priv-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
jlebon authored Feb 26, 2025
2 parents 4cc5e9d + 2446e1f commit 2067343
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 19 deletions.
10 changes: 10 additions & 0 deletions docs/operator-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Ignition has built-in support for fetching resources from the Amazon Simple Stor

Append `?versionId=<version>` to any of the URL formats to fetch the specified object version.

## Azure Blob Access

When Ignition runs on an Azure environment, it attempts to authenticate using the [Azure default credential chain](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential). If authentication is successful, these credentials are utilized to access resources hosted in Azure Blob Storage.

The URL format for accessing Azure Blob Storage is `https://<storageAccount>.blob.core.windows.net/<container>/<fileName>`. Ignition recognizes this pattern and parses it into its components. It then uses the [Azure Blob Storage API](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/storage/azblob#section-readme) to fetch resources.

When accessing a private Azure Blob Storage resource, ensure that the credentials assigned to the Azure VM have the necessary permissions. Typically, this means assigning at least a contributor role on the storage account.

If Ignition is not running on an Azure system or if Azure credentials are unavailable, it can still access public Azure Blobs by falling back to an anonymous HTTP fetch.

## HTTP headers

When fetching data from an HTTP URL for config references, CA references and file contents, additional headers can be attached to the request using the `httpHeaders` attribute. This allows downloading data from servers that require authentication or some additional parameters from your request.
Expand Down
22 changes: 20 additions & 2 deletions internal/providers/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"path/filepath"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
execUtil "github.com/coreos/ignition/v2/internal/exec/util"
Expand Down Expand Up @@ -71,8 +72,9 @@ var (

func init() {
platform.Register(platform.Provider{
Name: "azure",
Fetch: fetchConfig,
Name: "azure",
NewFetcher: newFetcher,
Fetch: fetchConfig,
})
}

Expand All @@ -81,6 +83,22 @@ func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
return fetchFromAzureMetadata(f)
}

// newFetcher returns a fetcher that tries to authenticate with Azure's default credential chain.
func newFetcher(l *log.Logger) (resource.Fetcher, error) {
// Read about NewDefaultAzureCredential https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential
// DefaultAzureCredential is a default credential chain for applications deployed to azure.
session, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
l.Info("could not retrieve any azure credentials: %v", err)
session = nil
}

return resource.Fetcher{
Logger: l,
AzSession: session,
}, nil
}

// fetchFromAzureMetadata first tries to fetch userData from IMDS then fallback on customData in case
// of empty config.
func fetchFromAzureMetadata(f *resource.Fetcher) (types.Config, report.Report, error) {
Expand Down
44 changes: 27 additions & 17 deletions internal/resource/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ type Fetcher struct {
// It is used when fetching resources from GCS.
GCSSession *storage.Client

// Azure credential to use when fetching resources from Azure Blob Storage.
// using DefaultAzureCredential()
AzSession *azidentity.DefaultAzureCredential

// Whether to only attempt fetches which can be performed offline. This
// currently only includes the "data" scheme. Other schemes will result in
// ErrNeedNet. In the future, we can improve on this by dropping this
Expand Down Expand Up @@ -151,9 +155,15 @@ func (f *Fetcher) FetchToBuffer(u url.URL, opts FetchOptions) ([]byte, error) {
dest := new(bytes.Buffer)
switch u.Scheme {
case "http", "https":
if strings.HasSuffix(u.Host, ".blob.core.windows.net") {
isAzureBlob := strings.HasSuffix(u.Host, ".blob.core.windows.net")
if f.AzSession != nil && isAzureBlob {
err = f.fetchFromAzureBlob(u, dest, opts)
} else {
if err != nil {
f.Logger.Info("could not fetch %s via Azure credentials: %v", u.String(), err)
f.Logger.Info("falling back to HTTP fetch")
}
}
if !isAzureBlob || f.AzSession == nil || err != nil {
err = f.fetchFromHTTP(u, dest, opts)
}
case "tftp":
Expand Down Expand Up @@ -213,13 +223,21 @@ func (f *Fetcher) Fetch(u url.URL, dest *os.File, opts FetchOptions) error {
if f.Offline && util.UrlNeedsNet(u) {
return ErrNeedNet
}

var err error
switch u.Scheme {
case "http", "https":
if strings.HasSuffix(u.Host, ".blob.core.windows.net") {
return f.fetchFromAzureBlob(u, dest, opts)
isAzureBlob := strings.HasSuffix(u.Host, ".blob.core.windows.net")
if f.AzSession != nil && isAzureBlob {
err = f.fetchFromAzureBlob(u, dest, opts)
if err != nil {
f.Logger.Info("could not fetch %s via Azure credentials: %v", u.String(), err)
f.Logger.Info("falling back to HTTP fetch")
}
}
if !isAzureBlob || f.AzSession == nil || err != nil {
err = f.fetchFromHTTP(u, dest, opts)
}
return f.fetchFromHTTP(u, dest, opts)
return err
case "tftp":
return f.fetchFromTFTP(u, dest, opts)
case "data":
Expand Down Expand Up @@ -579,16 +597,8 @@ func (f *Fetcher) parseAzureStorageUrl(u url.URL) (string, string, string, error
}

func (f *Fetcher) fetchFromAzureBlob(u url.URL, dest io.Writer, opts FetchOptions) error {
// Read about NewDefaultAzureCredential https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#DefaultAzureCredential
// DefaultAzureCredential is a default credential chain for applications deployed to azure.
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
f.Logger.Debug("failed to obtain Azure credential: %v", err)
return fmt.Errorf("failed to obtain Azure credential: %w", err)
}

// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// Create a context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

storageAccount, container, file, err := f.parseAzureStorageUrl(u)
Expand All @@ -597,7 +607,7 @@ func (f *Fetcher) fetchFromAzureBlob(u url.URL, dest io.Writer, opts FetchOption
}

// Create Azure Blob Storage client
storageClient, err := azblob.NewClient(storageAccount, cred, nil)
storageClient, err := azblob.NewClient(storageAccount, f.AzSession, nil)
if err != nil {
f.Logger.Debug("failed to create azblob client: %v", err)
return fmt.Errorf("failed to create azblob client: %w", err)
Expand Down

0 comments on commit 2067343

Please sign in to comment.