From a8f06c22fc661bc35eed1902b3ca36957b79b39e Mon Sep 17 00:00:00 2001 From: Nick Saika Date: Tue, 26 Mar 2024 14:08:48 +0000 Subject: [PATCH] providers: add "akamai" provider The "akamai" provider adds support for retrieving an ignition configuration from Akamai Connected Cloud's (a.k.a. Linode) [Metadata Service][1]. See https://github.com/flatcar/Flatcar/issues/1404 [1]: https://www.linode.com/docs/products/compute/compute-instances/guides/metadata/ --- docs/supported-platforms.md | 2 + internal/providers/akamai/akamai.go | 79 +++++++++++++++++++++++++++++ internal/resource/url.go | 4 ++ 3 files changed, 85 insertions(+) create mode 100644 internal/providers/akamai/akamai.go diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md index f0318339ab..8dc0feb139 100644 --- a/docs/supported-platforms.md +++ b/docs/supported-platforms.md @@ -6,6 +6,7 @@ nav_order: 8 Ignition is currently only supported for the following platforms: +* [Akamai Connected Cloud] (`akamai`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys and network configuration are handled separately. * [Alibaba Cloud] (`aliyun`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately. * [Apple Hypervisor] (`applehv`) - Ignition will read its configuration using an HTTP GET over a vsock connection with its host on port 1024. * [Amazon Web Services] (`aws`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately. @@ -36,6 +37,7 @@ Ignition is under active development, so this list may grow over time. For most cloud providers, cloud SSH keys and custom network configuration are handled by [Afterburn]. +[Akamai Connected Cloud]: https://www.linode.com [Alibaba Cloud]: https://www.alibabacloud.com/product/ecs [Apple Hypervisor]: https://developer.apple.com/documentation/hypervisor [Amazon Web Services]: https://aws.amazon.com/ec2/ diff --git a/internal/providers/akamai/akamai.go b/internal/providers/akamai/akamai.go new file mode 100644 index 0000000000..f316ded78b --- /dev/null +++ b/internal/providers/akamai/akamai.go @@ -0,0 +1,79 @@ +// Copyright 2024 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package akamai provides platform support for Akamai Connected Cloud +// (previously known as Linode). +package akamai + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + + "github.com/coreos/ignition/v2/config/v3_5_experimental/types" + "github.com/coreos/ignition/v2/internal/platform" + "github.com/coreos/ignition/v2/internal/providers/util" + "github.com/coreos/ignition/v2/internal/resource" + + "github.com/coreos/vcontext/report" +) + +func init() { + platform.Register(platform.Provider{ + Name: "akamai", + Fetch: fetchConfig, + Init: initFetcher, + }) +} + +var ( + tokenURL = url.URL{Scheme: "http", Host: "169.254.169.254", Path: "/v1/token"} + userdataURL = url.URL{Scheme: "http", Host: "169.254.169.254", Path: "/v1/user-data"} +) + +func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { + encoded, err := f.FetchToBuffer(userdataURL, resource.FetchOptions{}) + if err != nil { + return types.Config{}, report.Report{}, err + } + + // The Linode Metadata Service requires userdata to be base64-encoded + // when it is uploaded. + data := make([]byte, base64.StdEncoding.DecodedLen(len(encoded))) + if _, err := base64.StdEncoding.Decode(data, encoded); err != nil { + return types.Config{}, report.Report{}, fmt.Errorf("decode base64: %w", err) + } + + return util.ParseConfig(f.Logger, data) +} + +func initFetcher(f *resource.Fetcher) error { + token, err := f.FetchToBuffer(tokenURL, resource.FetchOptions{ + HTTPVerb: http.MethodPut, + Headers: http.Header{ + "Metadata-Token-Expiry-Seconds": []string{"3600"}, + }, + }) + + // NOTE: ErrNotFound could mean the instance is running in a region + // where the Metadata Service has not been deployed. + if err != nil { + return fmt.Errorf("generate metadata api token: %w", err) + } + + f.AkamaiMetadataToken = string(token) + + return nil +} diff --git a/internal/resource/url.go b/internal/resource/url.go index ad548a4976..0c7c662f5f 100644 --- a/internal/resource/url.go +++ b/internal/resource/url.go @@ -98,6 +98,10 @@ type Fetcher struct { // It is used when fetching resources from GCS. GCSSession *storage.Client + // AkamaiMetadataToken holds the token returned by the Metadata Service + // and is required for subsequent requests. + AkamaiMetadataToken string + // 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