From d9860bb223873f15871b526c32900349852af447 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Mon, 28 Aug 2017 07:37:01 -0700 Subject: [PATCH] Add support for bearer token auth in Prow Jenkins controller Not all Jenkins masters allow for basic auth. This patch adds support for bearer token auth in the Prow Jenkins client. Signed-off-by: Steve Kuznetsov --- prow/cluster/deck_deployment.yaml | 1 + prow/cluster/jenkins_deployment.yaml | 1 + prow/cmd/deck/main.go | 41 ++++++++++++++++++++++------ prow/cmd/jenkins-operator/main.go | 41 ++++++++++++++++++++++------ prow/jenkins/jenkins.go | 39 +++++++++++++++++++------- 5 files changed, 97 insertions(+), 26 deletions(-) diff --git a/prow/cluster/deck_deployment.yaml b/prow/cluster/deck_deployment.yaml index 96831bceca1db..c3a51d9837690 100644 --- a/prow/cluster/deck_deployment.yaml +++ b/prow/cluster/deck_deployment.yaml @@ -39,6 +39,7 @@ spec: containerPort: 8080 args: - --jenkins-url=$(JENKINS_URL) + - --jenkins-token=/etc/jenkins/jenkins - --build-cluster=/etc/cluster/cluster env: - name: JENKINS_URL diff --git a/prow/cluster/jenkins_deployment.yaml b/prow/cluster/jenkins_deployment.yaml index 388d9ab45f031..4bedc4ea693f1 100644 --- a/prow/cluster/jenkins_deployment.yaml +++ b/prow/cluster/jenkins_deployment.yaml @@ -28,6 +28,7 @@ spec: image: gcr.io/k8s-prow/jenkins-operator:0.39 args: - --dry-run=false + - --jenkins-token=/etc/jenkins/jenkins volumeMounts: - mountPath: /etc/jenkins name: jenkins diff --git a/prow/cmd/deck/main.go b/prow/cmd/deck/main.go index f2cc5b2ff982a..97f12715acc26 100644 --- a/prow/cmd/deck/main.go +++ b/prow/cmd/deck/main.go @@ -39,9 +39,10 @@ var ( configPath = flag.String("config-path", "/etc/config/config", "Path to config.yaml.") buildCluster = flag.String("build-cluster", "", "Path to file containing a YAML-marshalled kube.Cluster object. If empty, uses the local cluster.") - jenkinsURL = flag.String("jenkins-url", "", "Jenkins URL") - jenkinsUserName = flag.String("jenkins-user", "jenkins-trigger", "Jenkins username") - jenkinsTokenFile = flag.String("jenkins-token-file", "/etc/jenkins/jenkins", "Path to the file containing the Jenkins API token.") + jenkinsURL = flag.String("jenkins-url", "", "Jenkins URL") + jenkinsUserName = flag.String("jenkins-user", "jenkins-trigger", "Jenkins username") + jenkinsTokenFile = flag.String("jenkins-token-file", "", "Path to the file containing the Jenkins API token.") + jenkinsBearerTokenFile = flag.String("jenkins-bearer-token-file", "", "Path to the file containing the Jenkins API bearer token.") ) // Matches letters, numbers, hyphens, and underscores. @@ -70,14 +71,30 @@ func main() { } } + var ac *jenkins.AuthConfig var jc *jenkins.Client if *jenkinsURL != "" { - jenkinsSecretRaw, err := ioutil.ReadFile(*jenkinsTokenFile) - if err != nil { - logrus.WithError(err).Fatalf("Could not read token file.") + if *jenkinsTokenFile != "" { + if token, err := loadToken(*jenkinsTokenFile); err != nil { + logrus.WithError(err).Fatalf("Could not read token file.") + } else { + ac.Basic = &jenkins.BasicAuthConfig{ + User: *jenkinsUserName, + Token: token, + } + } + } else if *jenkinsBearerTokenFile != "" { + if token, err := loadToken(*jenkinsBearerTokenFile); err != nil { + logrus.WithError(err).Fatalf("Could not read token file.") + } else { + ac.BearerToken = &jenkins.BearerTokenAuthConfig{ + Token: token, + } + } + } else { + logrus.Fatal("An auth token for basic or bearer token auth must be supplied.") } - jenkinsToken := string(bytes.TrimSpace(jenkinsSecretRaw)) - jc = jenkins.NewClient(*jenkinsURL, *jenkinsUserName, jenkinsToken) + jc = jenkins.NewClient(*jenkinsURL, ac) } ja := &JobAgent{ @@ -95,6 +112,14 @@ func main() { logrus.WithError(http.ListenAndServe(":8080", nil)).Fatal("ListenAndServe returned.") } +func loadToken(file string) (string, error) { + raw, err := ioutil.ReadFile(file) + if err != nil { + return "", err + } + return string(bytes.TrimSpace(raw)), nil +} + func handleData(ja *JobAgent) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") diff --git a/prow/cmd/jenkins-operator/main.go b/prow/cmd/jenkins-operator/main.go index 912ac4b96129a..cbd669015e26d 100644 --- a/prow/cmd/jenkins-operator/main.go +++ b/prow/cmd/jenkins-operator/main.go @@ -34,9 +34,10 @@ import ( var ( configPath = flag.String("config-path", "/etc/config/config", "Path to config.yaml.") - jenkinsURL = flag.String("jenkins-url", "http://jenkins-proxy", "Jenkins URL") - jenkinsUserName = flag.String("jenkins-user", "jenkins-trigger", "Jenkins username") - jenkinsTokenFile = flag.String("jenkins-token-file", "/etc/jenkins/jenkins", "Path to the file containing the Jenkins API token.") + jenkinsURL = flag.String("jenkins-url", "http://jenkins-proxy", "Jenkins URL") + jenkinsUserName = flag.String("jenkins-user", "jenkins-trigger", "Jenkins username") + jenkinsTokenFile = flag.String("jenkins-token-file", "", "Path to the file containing the Jenkins API token.") + jenkinsBearerTokenFile = flag.String("jenkins-bearer-token-file", "", "Path to the file containing the Jenkins API bearer token.") _ = flag.String("github-bot-name", "", "Deprecated.") githubEndpoint = flag.String("github-endpoint", "https://api.github.com", "GitHub's API endpoint.") @@ -58,12 +59,28 @@ func main() { logrus.WithError(err).Fatal("Error getting kube client.") } - jenkinsSecretRaw, err := ioutil.ReadFile(*jenkinsTokenFile) - if err != nil { - logrus.WithError(err).Fatalf("Could not read Jenkins token file.") + var ac *jenkins.AuthConfig + if *jenkinsTokenFile != "" { + token, err := loadToken(*jenkinsTokenFile) + if err != nil { + logrus.WithError(err).Fatalf("Could not read token file.") + } + ac.Basic = &jenkins.BasicAuthConfig{ + User: *jenkinsUserName, + Token: token, + } + } else if *jenkinsBearerTokenFile != "" { + token, err := loadToken(*jenkinsBearerTokenFile) + if err != nil { + logrus.WithError(err).Fatalf("Could not read bearer token file.") + } + ac.BearerToken = &jenkins.BearerTokenAuthConfig{ + Token: token, + } + } else { + logrus.Fatal("An auth token for basic or bearer token auth must be supplied.") } - jenkinsToken := string(bytes.TrimSpace(jenkinsSecretRaw)) - jc := jenkins.NewClient(*jenkinsURL, *jenkinsUserName, jenkinsToken) + jc := jenkins.NewClient(*jenkinsURL, ac) oauthSecretRaw, err := ioutil.ReadFile(*githubTokenFile) if err != nil { @@ -95,3 +112,11 @@ func main() { logrus.Infof("Sync time: %v", time.Since(start)) } } + +func loadToken(file string) (string, error) { + raw, err := ioutil.ReadFile(file) + if err != nil { + return "", err + } + return string(bytes.TrimSpace(raw)), nil +} diff --git a/prow/jenkins/jenkins.go b/prow/jenkins/jenkins.go index be85110c4f1e4..9c57a2cc38a77 100644 --- a/prow/jenkins/jenkins.go +++ b/prow/jenkins/jenkins.go @@ -43,10 +43,25 @@ type Status struct { } type Client struct { - client *http.Client - baseURL string - user string - token string + client *http.Client + baseURL string + authConfig *AuthConfig +} + +// AuthConfig configures how we auth with Jenkins. +// Only one of the fields will be non-nil. +type AuthConfig struct { + Basic *BasicAuthConfig + BearerToken *BearerTokenAuthConfig +} + +type BasicAuthConfig struct { + User string + Token string +} + +type BearerTokenAuthConfig struct { + Token string } type BuildRequest struct { @@ -64,12 +79,11 @@ type Build struct { QueueURL *url.URL } -func NewClient(url, user, token string) *Client { +func NewClient(url string, authConfig *AuthConfig) *Client { return &Client{ - baseURL: url, - user: user, - token: token, - client: &http.Client{}, + baseURL: url, + authConfig: authConfig, + client: &http.Client{}, } } @@ -97,7 +111,12 @@ func (c *Client) doRequest(method, path string) (*http.Response, error) { if err != nil { return nil, err } - req.SetBasicAuth(c.user, c.token) + if c.authConfig.Basic != nil { + req.SetBasicAuth(c.authConfig.Basic.User, c.authConfig.Basic.Token) + } + if c.authConfig.BearerToken != nil { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.authConfig.BearerToken.Token)) + } return c.client.Do(req) }