Skip to content

Commit

Permalink
Merge pull request #1841 from nesv/provider.akamai
Browse files Browse the repository at this point in the history
  • Loading branch information
jlebon committed Apr 12, 2024
2 parents 44e3886 + 9b833b2 commit 5c7c765
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ nav_order: 9

### Features

- Support Akamai Connected Cloud (Linode)

### Changes

### Bug fixes
Expand Down
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
126 changes: 126 additions & 0 deletions internal/providers/akamai/akamai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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{"*/*"},
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)
}

// defaultTokenTTL is the time-to-live (TTL; in seconds) for an authorization
// token retrieved from the Metadata Service API's "PUT /v1/token" endpoint.
const defaultTokenTTL = "300"

// 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{defaultTokenTTL},
},
})
if err != nil {
return "", fmt.Errorf("fetch to buffer: %w", err)
}

p = bytes.TrimSpace(p)
if len(p) == 0 {
return "", errors.New("received 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 5c7c765

Please sign in to comment.