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

providers/azure: support Azure IMDS #39

Closed
wants to merge 3 commits into from
Closed

Conversation

tormath1
Copy link
Contributor

@tormath1 tormath1 commented Apr 12, 2022

In this PR, we add support for Azure IMDS on top of existing CustomData. If it fails to fetch base64 encoded userData Ignition will fallback on CustomData.

provider/azure: try to fetch user-data from IMDS could be upstreamed once the tests / validation are OK.

Note for reviewers

  • the last two commits: config: update headers_test to stop using strings.Title() and ci: add testing with Go 1.18 are from the upstream - it makes the CI happy.

Copy link
Contributor

@bgilbert bgilbert left a comment

Choose a reason for hiding this comment

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

👋 Thanks for working on this! A few comments from upstream.

@@ -542,6 +543,8 @@ func (f *Fetcher) uncompress(r io.Reader, opts FetchOptions) (io.ReadCloser, err
return ioutil.NopCloser(r), nil
case "gzip":
return gzip.NewReader(r)
case "base64":
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer that we not do this here, and handle base64-decoding in the Azure provider instead. Otherwise this function will be a mix of cases that are supported by the Resource.Compression spec field and cases that are supported only for internal convenience.

@@ -34,5 +34,5 @@ const (

// FetchConfig implements the platform.NewFetcher interface.
func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
return azure.FetchFromOvfDevice(f, []string{CDS_FSTYPE_UDF, CDS_FSTYPE_ISO9660})
return azure.FetchFromAzureMetadata(f, []string{CDS_FSTYPE_UDF, CDS_FSTYPE_ISO9660})
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you confirmed that Azure Stack always supports the metadata service? If not, we should probably keep FetchFromOvfDevice here, and make FetchFromAzureMetadata a wrapper around it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right - thanks for the heads-up ! According to the documentation1:

The Azure Instance Metadata Service isn't supported on Azure Stack Hub.

Footnotes

  1. https://docs.microsoft.com/en-us/azure-stack/user/azure-stack-vm-considerations

@@ -56,15 +57,38 @@ const (
CDS_FSTYPE_UDF = "udf"
)

// FetchConfig wraps FetchOvfDevice to implement the platform.NewFetcher interface.
var (
imdsURL = url.URL{
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: maybe imdsUserdataURL?

// we first try to config from IMDS, in case of failure we fallback on the custom data.
data, err := f.FetchToBuffer(imdsURL, resource.FetchOptions{Compression: "base64"})
if err == nil {
return util.ParseConfig(logger, data)
Copy link
Contributor

Choose a reason for hiding this comment

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

For user debugging purposes, let's log at Info level if we've read the config from userdata and if we've read it from custom data.


// we first try to config from IMDS, in case of failure we fallback on the custom data.
data, err := f.FetchToBuffer(imdsURL, resource.FetchOptions{Compression: "base64"})
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's any error, we're falling through and failing to report it. I assume the metadata service is always available, and returns 404 if userdata is missing? If so, we should probably fall through on resource.ErrNotFound, and error out otherwise.

Copy link
Contributor Author

@tormath1 tormath1 Apr 14, 2022

Choose a reason for hiding this comment

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

Just checked and if userdata is missing it seems to return 200 with an empty string:

curl -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text" -v
*   Trying 169.254.169.254:80...
* Connected to 169.254.169.254 (169.254.169.254) port 80 (#0)
> GET /metadata/instance/compute/userData?api-version=2021-01-01&format=text HTTP/1.1
> Host: 169.254.169.254
> User-Agent: curl/7.79.1
> Accept: */*
> Metadata:true
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
< Server: IMDS/150.870.65.597
< Date: Thu, 14 Apr 2022 09:07:49 GMT
< Content-Length: 0
< 
* Connection #0 to host 169.254.169.254 left intact

EDIT: added a check for data length

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, okay. We're still falling through, though. Presumably we should be doing something like:

	} else if err != errors.ErrEmpty {
		return types.Config{}, report.Report{}, err
	}

Otherwise, an error accessing the IMDS server could cause Ignition to conclude that no userdata was provided and allow the node to boot unconfigured.

@tormath1 tormath1 force-pushed the tormath1/azuredata branch 2 times, most recently from 6f0f27a to d419951 Compare April 14, 2022 13:20

// fetchFromAzureMetadata first tries to fetch userData from IMDS then fallback on customData in case
// of failure or empty config.
func fetchFromAzureMetadata(f *resource.Fetcher) (types.Config, report.Report, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: let's move this above the previous function so the file reads consistently from top to bottom.

n := len(data)

if n == 0 {
return nil, fmt.Errorf("userdata is empty")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return nil, fmt.Errorf("userdata is empty")
return nil, errors.ErrEmpty

return nil, fmt.Errorf("fetching to buffer: %w", err)
}

n := len(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: unnecessary variable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

declared because because used twice:

  • to check if length is 0
  • later to decode base64

Copy link
Contributor

Choose a reason for hiding this comment

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

Argh, yup, I had missed that.


// we first try to config from IMDS, in case of failure we fallback on the custom data.
data, err := f.FetchToBuffer(imdsURL, resource.FetchOptions{Compression: "base64"})
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, okay. We're still falling through, though. Presumably we should be doing something like:

	} else if err != errors.ErrEmpty {
		return types.Config{}, report.Report{}, err
	}

Otherwise, an error accessing the IMDS server could cause Ignition to conclude that no userdata was provided and allow the node to boot unconfigured.

@tormath1
Copy link
Contributor Author

@bgilbert thanks for the review - comments have been addressed and a CI test is running.
One additional change has been made in order to use RawQuery in the URL definition. Otherwise string URL was rendered like: http//169.254.169.254/metadata/instance/compute/userData%3Fapi-version=2021-01-01&format=text which was leading to an error 400 / Bad Request.

tormath1 and others added 3 commits April 19, 2022 20:12
Azure userdata fetching is now divided into two parts:
* try to fetch userdata from IMDS
* in case of failure: try to fetch custom data from CDROM

Signed-off-by: Mathieu Tortuyaux <mtortuyaux@microsoft.com>
Update `golangci-lint` to version v1.45.0 which has support for go 1.18

Signed-off-by: Mathieu Tortuyaux <mtortuyaux@microsoft.com>
`strings.Title()` is deprecated in go 1.18, so change the affected
tests to use different constants

Signed-off-by: Mathieu Tortuyaux <mtortuyaux@microsoft.com>
@tormath1 tormath1 marked this pull request as ready for review April 19, 2022 19:15
@tormath1 tormath1 requested a review from a team April 19, 2022 19:15
@tormath1
Copy link
Contributor Author

Copy link
Contributor

@bgilbert bgilbert left a comment

Choose a reason for hiding this comment

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

LGTM!

@pothos
Copy link
Member

pothos commented Apr 22, 2022

With the upstream PR merged I would say that we can either cherry pick directly or better update to a newer version since we have to do this anyway sooner or later

@tormath1 tormath1 closed this Apr 28, 2022
@tormath1 tormath1 deleted the tormath1/azuredata branch April 28, 2022 16:04
@tormath1
Copy link
Contributor Author

it should come with the next release of ignition in Flatcar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants