forked from coreos/ignition
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters