diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go index bdc904c7b..e6cacb7d6 100644 --- a/cmd/osbuild-worker/main.go +++ b/cmd/osbuild-worker/main.go @@ -142,6 +142,8 @@ func main() { Authentication *struct { OAuthURL string `toml:"oauth_url"` OfflineTokenPath string `toml:"offline_token"` + ClientId string `toml:"client_id"` + ClientSecretPath string `toml:"client_secret"` } `toml:"authentication"` RelaxTimeoutFactor uint `toml:"RelaxTimeoutFactor"` BasePath string `toml:"base_path"` @@ -205,7 +207,7 @@ func main() { BaseURL: address, BasePath: config.BasePath, }) - } else if config.Authentication != nil && config.Authentication.OfflineTokenPath != "" { + } else if config.Authentication != nil { var conf *tls.Config conConf := &connectionConfig{ CACertFile: "/etc/osbuild-composer/ca-crt.pem", @@ -217,13 +219,22 @@ func main() { } } - t, err := ioutil.ReadFile(config.Authentication.OfflineTokenPath) - if err != nil { - logrus.Fatalf("Could not read offline token: %v", err) + token := "" + if config.Authentication.OfflineTokenPath != "" { + t, err := ioutil.ReadFile(config.Authentication.OfflineTokenPath) + if err != nil { + logrus.Fatalf("Could not read offline token: %v", err) + } + token = strings.TrimSpace(string(t)) } - token := strings.TrimSpace(string(t)) - if token != "" && config.Authentication.OAuthURL == "" { - logrus.Fatal("OAuth URL should be specified together with the offline token") + + clientSecret := "" + if config.Authentication.ClientSecretPath != "" { + cs, err := ioutil.ReadFile(config.Authentication.ClientSecretPath) + if err != nil { + logrus.Fatalf("Could not read client secret: %v", err) + } + clientSecret = strings.TrimSpace(string(cs)) } client, err = worker.NewClient(worker.ClientConfig{ @@ -231,6 +242,8 @@ func main() { TlsConfig: conf, OfflineToken: token, OAuthURL: config.Authentication.OAuthURL, + ClientId: config.Authentication.ClientId, + ClientSecret: clientSecret, BasePath: config.BasePath, }) if err != nil { diff --git a/internal/worker/client.go b/internal/worker/client.go index 833d45cd4..3e1384784 100644 --- a/internal/worker/client.go +++ b/internal/worker/client.go @@ -24,6 +24,8 @@ type Client struct { offlineToken string oAuthURL string accessToken string + clientId string + clientSecret string tokenMu sync.RWMutex } @@ -33,6 +35,8 @@ type ClientConfig struct { TlsConfig *tls.Config OfflineToken string OAuthURL string + ClientId string + ClientSecret string BasePath string } @@ -76,6 +80,15 @@ func NewClient(conf ClientConfig) (*Client, error) { panic(err) } + if conf.OAuthURL != "" { + if conf.ClientId == "" { + return nil, fmt.Errorf("OAuthURL token url specified but no client id") + } + if conf.OfflineToken == "" && conf.ClientSecret == "" { + return nil, fmt.Errorf("OAuthURL token url specified but no client secret or offline token") + } + } + requester := &http.Client{} transport := http.DefaultTransport.(*http.Transport).Clone() if conf.TlsConfig != nil { @@ -88,6 +101,8 @@ func NewClient(conf ClientConfig) (*Client, error) { requester: requester, offlineToken: conf.OfflineToken, oAuthURL: conf.OAuthURL, + clientId: conf.ClientId, + clientSecret: conf.ClientSecret, }, nil } @@ -118,19 +133,21 @@ func NewClientUnix(conf ClientConfig) *Client { } } -// Note: Only call this function with Client.tokenMu locked! func (c *Client) refreshAccessToken() error { c.tokenMu.Lock() defer c.tokenMu.Unlock() - if c.offlineToken == "" || c.oAuthURL == "" { - return fmt.Errorf("No offline token or oauth url available") - } - data := url.Values{} - data.Set("grant_type", "refresh_token") - data.Set("client_id", "rhsm-api") - data.Set("refresh_token", c.offlineToken) + if c.offlineToken != "" { + data.Set("grant_type", "refresh_token") + data.Set("client_id", c.clientId) + data.Set("refresh_token", c.offlineToken) + } + if c.clientSecret != "" { + data.Set("grant_type", "client_credentials") + data.Set("client_id", c.clientId) + data.Set("client_secret", c.clientSecret) + } resp, err := http.PostForm(c.oAuthURL, data) if err != nil { @@ -159,8 +176,7 @@ func (c *Client) NewRequest(method, url string, headers map[string]string, body return nil, err } - // If we're using OAUTH, add the Bearer token - if c.offlineToken != "" { + if c.oAuthURL != "" { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token())) } @@ -173,7 +189,7 @@ func (c *Client) NewRequest(method, url string, headers map[string]string, body return nil, err } - if resp.StatusCode == http.StatusUnauthorized { + if resp.StatusCode == http.StatusUnauthorized && c.oAuthURL != "" { err = c.refreshAccessToken() if err != nil { return nil, err diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go index 2fd0658ec..36bf653ba 100644 --- a/internal/worker/server_test.go +++ b/internal/worker/server_test.go @@ -399,6 +399,7 @@ func TestOAuth(t *testing.T) { client, err := worker.NewClient(worker.ClientConfig{ BaseURL: proxySrv.URL, TlsConfig: nil, + ClientId: "rhsm-api", OfflineToken: offlineToken, OAuthURL: oauthSrv.URL, BasePath: "/api/image-builder-worker/v1", diff --git a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/client_credentials.sh b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/client_credentials.sh new file mode 100755 index 000000000..10cbb6cda --- /dev/null +++ b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/client_credentials.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -eo pipefail +source /tmp/cloud_init_vars + +echo "Writing client credentials." + +if [[ -z "$CLIENT_CREDENTIALS_ARN" ]]; then + echo "CLIENT_CREDENTIALS_ARN not defined, skipping." + exit 0 +fi + +# get client credentials +/usr/local/bin/aws secretsmanager get-secret-value \ + --endpoint-url "${SECRETS_MANAGER_ENDPOINT_URL}" \ + --secret-id "${CLIENT_CREDENTIALS_ARN}" | jq -r ".SecretString" > /tmp/client-credentials.json + +CLIENT_ID=$(jq -r ".client_id" /tmp/client-credentials.json) +jq -r ".client_secret" /tmp/client-credentials.json > /etc/osbuild-worker/client-secret +rm -f /tmp/client-credentials.json + +sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF +[authentication] +oauth_url = "https://identity.api.openshift.com/auth/realms/rhoas/protocol/openid-connect/token" +client_id = "${CLIENT_ID}" +client_secret = "/etc/osbuild-worker/client-secret" +EOF diff --git a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/offline_token.sh b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/offline_token.sh index ba6e5a470..c47f1b563 100755 --- a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/offline_token.sh +++ b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/offline_token.sh @@ -1,9 +1,14 @@ #!/bin/bash -set -euo pipefail +set -eo pipefail source /tmp/cloud_init_vars echo "Writing offline token." +if [[ -z "$OFFLINE_TOKEN_ARN" ]]; then + echo "OFFLINE_TOKEN_ARN not defined, skipping." + exit 0 +fi + # get offline token /usr/local/bin/aws secretsmanager get-secret-value \ --endpoint-url "${SECRETS_MANAGER_ENDPOINT_URL}" \ @@ -15,5 +20,6 @@ rm -f /tmp/offline-token.json sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF [authentication] oauth_url = "https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token" +client_id = "rhsm-api" offline_token = "/etc/osbuild-worker/offline-token" EOF diff --git a/templates/packer/ansible/roles/common/files/worker-initialization.service b/templates/packer/ansible/roles/common/files/worker-initialization.service index 7bc04f481..cbb6f7558 100644 --- a/templates/packer/ansible/roles/common/files/worker-initialization.service +++ b/templates/packer/ansible/roles/common/files/worker-initialization.service @@ -10,6 +10,7 @@ ExecStart=touch /etc/worker-first-boot ExecStart=/usr/local/libexec/worker-initialization-scripts/set_hostname.sh ExecStart=/usr/local/libexec/worker-initialization-scripts/vector.sh ExecStart=/usr/local/libexec/worker-initialization-scripts/offline_token.sh +ExecStart=/usr/local/libexec/worker-initialization-scripts/client_credentials.sh ExecStart=/usr/local/libexec/worker-initialization-scripts/subscription_manager.sh ExecStart=/usr/local/libexec/worker-initialization-scripts/get_aws_creds.sh ExecStart=/usr/local/libexec/worker-initialization-scripts/get_azure_creds.sh diff --git a/test/cases/api.sh b/test/cases/api.sh index 852e6c053..bb7ddd209 100755 --- a/test/cases/api.sh +++ b/test/cases/api.sh @@ -1294,6 +1294,7 @@ EOF cat </dev/null <