Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update azure priv fetch #2012

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 resources hosted on public Azure Blobs anonymously by falling back on 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
24 changes: 22 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,24 @@ 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)
return resource.Fetcher{
Logger: l,
}, 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 || err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it's an Azure blob but we don't have a session?

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 || 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
Loading