Skip to content

Commit

Permalink
providers: add "akamai" provider
Browse files Browse the repository at this point in the history
The "akamai" provider adds support for retrieving an ignition
configuration from Akamai Connected Cloud's (a.k.a. Linode) [Metadata
Service][1].

See: flatcar/Flatcar#1404
See: coreos/fedora-coreos-tracker#1701
Fixes: coreos#1508

[1]: https://www.linode.com/docs/products/compute/compute-instances/guides/metadata/
  • Loading branch information
Nick Saika committed Apr 4, 2024
1 parent 8caa117 commit eb2f79e
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/supported-platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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/
Expand Down
122 changes: 122 additions & 0 deletions internal/providers/akamai/akamai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 (
"bytes"
"encoding/base64"
"errors"
"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,
})
}

// HTTP headers.
const (
// tokenTTLHeader is the name of the HTTP request header that must be
// set when making requests to [tokenURL] or [tokenURL6].
tokenTTLHeader = "Metadata-Token-Expiry-Seconds"

// tokenHeader is the name of the HTTP request header that callers must
// set when making requests to [userdataURL] or [userdataURL6].
tokenHeader = "Metadata-Token"
)

var (
// IPv4 URLs.
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"}

// IPv6 URLs (for reference).
// tokenURL6 = url.URL{Scheme: "http", Host: "[fd00:a9fe:a9fe::1]", Path: "/v1/token"}
// userdataURL6 = url.URL{Scheme: "http", Host: "[fd00:a9fe:a9fe::1]", Path: "/v1/user-data"}
)

func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
if f.Offline {
return types.Config{}, report.Report{}, resource.ErrNeedNet
}

token, err := getToken(f)
if err != nil {
return types.Config{}, report.Report{}, fmt.Errorf("get token: %w", err)
}

// NOTE: If we do not explicitly set the "Accept" header, it will be
// set by FetchToBuffer to a value that the Linode Metadata Service
// does not accept.
encoded, err := f.FetchToBuffer(userdataURL, resource.FetchOptions{
Headers: http.Header{
"Accept": []string{"text/plain"},
tokenHeader: []string{string(token)},
},
})
if err != nil {
return types.Config{}, report.Report{}, fmt.Errorf("fetch userdata: %w", err)
}

// The Linode Metadata Service requires userdata to be base64-encoded
// when it is uploaded, so we will have to decode the response.
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)
}

// getToken retrieves an authorization token to use for subsequent requests to
// Linode's Metadata Service.
// The returned token must be provided in the [tokenHeader] request header.
func getToken(f *resource.Fetcher) (token string, err error) {
// NOTE: This is using "text/plain" for content negotiation, just to
// skip the need to decode a JSON response.
// In the future, the accepted content type should probably become
// "application/vnd.coreos.ignition+json", but that will require
// support from Linode's Metadata Service API.
p, err := f.FetchToBuffer(tokenURL, resource.FetchOptions{
HTTPVerb: http.MethodPut,
Headers: http.Header{
"Accept": []string{"text/plain"},
tokenTTLHeader: []string{"3600"},
},
})
if err != nil {
return "", fmt.Errorf("fetch to buffer: %w", err)
}

p = bytes.TrimSpace(p)
if len(p) == 0 {
return "", errors.New("receieved an empty token")
}

return string(p), nil
}
1 change: 1 addition & 0 deletions internal/register/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package register

import (
_ "github.com/coreos/ignition/v2/internal/providers/akamai"
_ "github.com/coreos/ignition/v2/internal/providers/aliyun"
_ "github.com/coreos/ignition/v2/internal/providers/applehv"
_ "github.com/coreos/ignition/v2/internal/providers/aws"
Expand Down

0 comments on commit eb2f79e

Please sign in to comment.