From 45340e705f50a6f56f074b2a96bc8b5be866710a Mon Sep 17 00:00:00 2001 From: Sander Descamps Date: Fri, 21 Jul 2023 14:04:56 +0200 Subject: [PATCH 1/3] feat: add userpass support --- rundeck/provider.go | 32 ++++++++++- rundeck/provider_test.go | 12 ++++- rundeck/token.go | 113 +++++++++++++++++++++++++++++++++++++++ rundeck/token_test.go | 27 ++++++++++ 4 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 rundeck/token.go create mode 100644 rundeck/token_test.go diff --git a/rundeck/provider.go b/rundeck/provider.go index cf530be582..d6b3313bf4 100644 --- a/rundeck/provider.go +++ b/rundeck/provider.go @@ -28,10 +28,22 @@ func Provider() terraform.ResourceProvider { }, "auth_token": { Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_TOKEN", nil), Description: "Auth token to use with the Rundeck API.", }, + "auth_username": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_USERNAME", nil), + Description: "Username used to request a tiken for the Rundeck API.", + }, + "auth_password": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_PASSWORD", nil), + Description: "Password used to request a tiken for the Rundeck API.", + }, }, ResourcesMap: map[string]*schema.Resource{ @@ -58,7 +70,23 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, error } - token := d.Get("auth_token").(string) + _, okToken := d.GetOk("auth_token") + _, okUsername := d.GetOk("auth_username") + _, okPassword := d.GetOk("auth_password") + + var token string + + if okToken { + token = d.Get("auth_token").(string) + } else if okUsername && okPassword { + t, err := getToken(d) + token = t + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("auth_token need to be set of auth_username and auth_password") + } client := rundeck.NewRundeckWithBaseURI(apiURL.String()) client.Authorizer = &auth.TokenAuthorizer{Token: token} diff --git a/rundeck/provider_test.go b/rundeck/provider_test.go index d66fe0d537..e529c6345b 100644 --- a/rundeck/provider_test.go +++ b/rundeck/provider_test.go @@ -92,7 +92,15 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("RUNDECK_URL"); v == "" { t.Fatal("RUNDECK_URL must be set for acceptance tests") } - if v := os.Getenv("RUNDECK_AUTH_TOKEN"); v == "" { - t.Fatal("RUNDECK_AUTH_TOKEN must be set for acceptance tests") + + token := os.Getenv("RUNDECK_AUTH_TOKEN") + username := os.Getenv("RUNDECK_AUTH_USERNAME") + password := os.Getenv("RUNDECK_AUTH_PASSWORD") + if !(token != "" || (username != "" && password != "")) { + t.Logf("RUNDECK_AUTH_TOKEN=%s", token) + t.Logf("RUNDECK_AUTH_USERNAME=%s", username) + t.Logf("RUNDECK_AUTH_PASSWORD=%s", password) + t.Fatal("RUNDECK_AUTH_TOKEN must be set for acceptance tests or RUNDECK_AUTH_USERNAME and RUNDECK_AUTH_PASSWORD") } + } diff --git a/rundeck/token.go b/rundeck/token.go new file mode 100644 index 0000000000..012d9c6351 --- /dev/null +++ b/rundeck/token.go @@ -0,0 +1,113 @@ +package rundeck + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type TokenResp struct { + User string `json:"user"` + Token string `json:"token"` + ID string `json:"id"` + Creator string `json:"creator"` + Name string `json:"name"` + Expiration time.Time `json:"expiration"` + Roles []string `json:"roles"` + Expired bool `json:"expired"` +} + +func getToken(d *schema.ResourceData) (string, error) { + urlP, _ := d.Get("url").(string) + apiVersion, _ := d.Get("api_version").(string) + username, _ := d.Get("auth_username").(string) + password, _ := d.Get("auth_password").(string) + + return _getToken(urlP, apiVersion, username, password) + +} + +func _getToken(urlP string, apiVersion string, username string, password string) (string, error) { + + secCheckUrlString := fmt.Sprintf("%s/j_security_check", urlP) + secCheckUrl, err := url.Parse(secCheckUrlString) + + if err != nil { + return "", err + } + + jar, err := cookiejar.New(nil) + if err != nil { + return "", err + } + + client := &http.Client{ + Jar: jar, + } + + data := url.Values{ + "j_username": {username}, + "j_password": {password}, + } + _, err = client.PostForm(secCheckUrl.String(), data) + if err != nil { + return "", err + } + + tokenUrlString := fmt.Sprintf("%s/api/%s/tokens", urlP, "43") + tokenUrl, err := url.Parse(tokenUrlString) + + if err != nil { + return "", err + } + + tokenBody := map[string]interface{}{} + tokenBody["user"] = username + tokenBody["roles"] = []string{"*"} + tokenBody["duration"] = "0" + tokenBody["name"] = "terraform-token" + tokenBodyJson, err := json.Marshal(tokenBody) + if err != nil { + return "", err + } + + req, err := http.NewRequest("POST", tokenUrl.String(), bytes.NewBuffer(tokenBodyJson)) + if err != nil { + return "", err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + + resp, err := client.Do(req) + if resp.StatusCode < 200 || resp.StatusCode > 299 { + body, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("statuscode %d\n%s", resp.StatusCode, string(body)) + } else if err != nil { + return "", err + } + + tokenResp := &TokenResp{} + err = json.NewDecoder(resp.Body).Decode(tokenResp) + if err != nil { + return "", err + } + + if version, _ := strconv.Atoi(apiVersion); version >= 19 { + return tokenResp.Token, nil + } else if tokenResp.Token != "" { + return tokenResp.Token, nil + } else if tokenResp.ID != "" { + return tokenResp.ID, nil + } + + return tokenResp.Token, nil + +} diff --git a/rundeck/token_test.go b/rundeck/token_test.go new file mode 100644 index 0000000000..9d35b51d22 --- /dev/null +++ b/rundeck/token_test.go @@ -0,0 +1,27 @@ +package rundeck + +import ( + "os" + "testing" +) + +func TestAccToken(t *testing.T) { + testAccPreCheck(t) + + username := os.Getenv("RUNDECK_AUTH_USERNAME") + password := os.Getenv("RUNDECK_AUTH_PASSWORD") + apiVersion := os.Getenv("RUNDECK_API_VERSION") + if apiVersion == "" { + apiVersion = "14" + } + url := os.Getenv("RUNDECK_URL") + + if username != "" && password != "" { + _, err := _getToken(url, apiVersion, username, password) + if err != nil { + t.Fatalf("failed to get a token: %s", err) + } + } else { + t.Logf("Can not run TestAccToken test as there is no username and password defined") + } +} From a62846a4b7fe39d08faf84c6b780a13ab0ffdd06 Mon Sep 17 00:00:00 2001 From: Forrest Evans Date: Fri, 6 Oct 2023 17:16:00 -0700 Subject: [PATCH 2/3] Add Docs for UserName Password --- website/docs/index.html.markdown | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 89d425f85a..eeb8123d99 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -22,9 +22,17 @@ The provider configuration block accepts the following arguments: minium supported version. May alternatively be set via the ``RUNDECK_API_VERSION`` environment variable. -* ``auth_token`` - (Required) The API auth token to use when making requests. May alternatively +* ``auth_token`` - The API auth token to use when making requests. May alternatively be set via the ``RUNDECK_AUTH_TOKEN`` environment variable. +**OR** + +* ``auth_username`` - Local Login User Name. +* ``auth_password`` - Local Login Passwrod. + +> Note: Username and Password auth will not work with SSO configured systems. It relies on local Rundeck accounts. + + Use the navigation to the left to read about the available resources. ## Example Usage @@ -38,7 +46,7 @@ terraform { required_providers { rundeck = { source = "rundeck/rundeck" - version = "0.4.2" + version = "0.4.4" } } } From dffd61efc6402e6fdf010b490e41824c67702bc3 Mon Sep 17 00:00:00 2001 From: Forrest Evans Date: Fri, 6 Oct 2023 17:16:41 -0700 Subject: [PATCH 3/3] Update index.html.markdown --- website/docs/index.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index eeb8123d99..2936b0bdd0 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -23,14 +23,14 @@ The provider configuration block accepts the following arguments: environment variable. * ``auth_token`` - The API auth token to use when making requests. May alternatively - be set via the ``RUNDECK_AUTH_TOKEN`` environment variable. + be set via the ``RUNDECK_AUTH_TOKEN`` environment variable. (RECOMMENDED) **OR** * ``auth_username`` - Local Login User Name. * ``auth_password`` - Local Login Passwrod. -> Note: Username and Password auth will not work with SSO configured systems. It relies on local Rundeck accounts. +> Note: Username and Password auth will not work with SSO configured systems. It relies on local Rundeck accounts. Please be sensitive to keeping passwords in your plan files! Use the navigation to the left to read about the available resources.