diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9f85ca..758ae17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,10 +8,10 @@ jobs: name: GoReleaser runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.14 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.14 id: go - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 155adde..b7618d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,10 +8,10 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.14 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.14 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/README.md b/README.md index 99a4e3e..c2e41e1 100644 --- a/README.md +++ b/README.md @@ -28,27 +28,34 @@ You need Terraform v0.12 and an Azure AD tenant with the admin privilege. ## Provider configuration +The provider has the configuration with the following default values. +You can modify the default values with the corresponding environment variables. + ```hcl provider "msgraph" { - tenant_id = "common" - client_id = "82492584-8587-4e7d-ad48-19546ce8238f" - client_secret = "" // empty for device code authorization - token_cache_path = "token_cache.json" + tenant_id = "common" // env:ARM_TENANT_ID + client_id = "82492584-8587-4e7d-ad48-19546ce8238f" // env:ARM_CLIENT_ID + client_secret = "" // env:ARM_CLIENT_SECRET + token_cache_path = "token_cache.json" // env:ARM_TOKEN_CACHE_PATH + console_device_path = "/dev/tty" // env:ARM_CONSOLE_DEVICE_PATH } ``` -The configuration above (`client_id = "82492584-8587-4e7d-ad48-19546ce8238f"`) is +The default configuration above is to use the public client defined in `l0w.dev` tenant with the permission `Directory.AccessAsUser.All`. You can use it to make terraform to access your tenant's directory with the delegated privilege. When `client_secret` is empty, the provider attempts [the device code authorization](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code). -You can see the following message in the debug log on the first invocation of `terraform plan`: +You can see the following message on the first invocation of `terraform plan`: ```console -$ TF_LOG=DEBUG terraform plan -... -2020-02-09T03:55:33.204+0900 [DEBUG] plugin.terraform-provider-msgraph: 2020/02/09 03:55:33 To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code GEXSRT5LT to authenticate. +$ terraform plan +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but will not be +persisted to local or remote state storage. + +To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code GNATKX4J8 to authenticate. ``` Open https://microsoft.com/devicelogin with your web browser and enter the code to proceed the authorization steps. @@ -59,9 +66,17 @@ You can also specify an Azure Blob URL with SAS for `token_cache_path`. It's recommended to pass it via `ARM_TOKEN_CACHE_PATH` envvar since the SAS is considered sensitive information that should be hidden. +The provider opens `console_device_path` to prompt the instruction of the device code authorization. +It might have no acccess to `/dev/tty` in the restricted environment like GitLab CI runner. +You can workaround it by fd number device and redirection with the shell as follows: + +```console +$ 99>&2 ARM_CONSOLE_DEVICE_PATH=/dev/fd/99 terraform plan +``` + ## How to test -Terraform v0.12 and Go v1.13 are required. +Terraform v0.12 and Go v1.14 are required. It's strongly recommended to acquire a developer sandbox tenant by joining [the Office 365 developer program](https://developer.microsoft.com/en-us/office/dev-program). @@ -108,7 +123,8 @@ $ TF_LOG=DEBUG terraform apply - [ ] Support importing - [ ] Code auto-generation based on the API metadata - [x] Persist OAuth2 tokens in backend storage (Azure Blob Storage) -- [ ] Better device auth grant experience (no `TF_LOG=DEBUG`) +- [x] Better device auth grant experience (no `TF_LOG=DEBUG`) - [ ] Unit testing -- [ ] CI/CD +- [x] CI/CD (GoReleaser) - [ ] Manuals +- [ ] Publish to the Terraform registry (#1) diff --git a/msgraph/provider.go b/msgraph/provider.go index 863f3a3..ec87983 100644 --- a/msgraph/provider.go +++ b/msgraph/provider.go @@ -2,6 +2,7 @@ package msgraph import ( "context" + "fmt" "log" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -44,6 +45,12 @@ func Provider() *schema.Provider { Required: true, DefaultFunc: schema.EnvDefaultFunc("ARM_TOKEN_CACHE_PATH", defaultTokenCachePath), }, + "console_device_path": { + Type: schema.TypeString, + Description: "Console Device Path", + Required: true, + DefaultFunc: schema.EnvDefaultFunc("ARM_CONSOLE_DEVICE_PATH", ""), + }, }, DataSourcesMap: map[string]*schema.Resource{ "msgraph_user": dataUserResource(), @@ -76,7 +83,18 @@ func configureFunc(r *schema.ResourceData) (interface{}, error) { } } else { m.LoadFile(tokenCachePath) - ts, err = m.DeviceAuthorizationGrant(ctx, tenantID, clientID, []string{"offline_access", msauth.DefaultMSGraphScope}, func(dc *msauth.DeviceCode) error { log.Println(dc.Message); return nil }) + ts, err = m.DeviceAuthorizationGrant(ctx, tenantID, clientID, []string{"offline_access", msauth.DefaultMSGraphScope}, + func(dc *msauth.DeviceCode) error { + log.Println(dc.Message) + con, err := openConsole(r.Get("console_device_path").(string)) + if err == nil { + fmt.Fprintln(con, dc.Message) + con.Close() + } else { + log.Printf("Failed to open console: %s", err) + } + return nil + }) if err != nil { return nil, err } diff --git a/msgraph/provider_unix.go b/msgraph/provider_unix.go new file mode 100644 index 0000000..3568b87 --- /dev/null +++ b/msgraph/provider_unix.go @@ -0,0 +1,15 @@ +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package msgraph + +import ( + "io" + "os" +) + +func openConsole(path string) (io.WriteCloser, error) { + if path == "" { + path = "/dev/tty" + } + return os.OpenFile(path, os.O_RDWR, 0) +} diff --git a/msgraph/provider_windows.go b/msgraph/provider_windows.go new file mode 100644 index 0000000..4537946 --- /dev/null +++ b/msgraph/provider_windows.go @@ -0,0 +1,14 @@ +package msgraph + +import ( + "io" + "os" +) + +func openConsole(path string) (io.WriteCloser, error) { + if path == "" { + // XXX: I don't know if this actually works... + path = "CON" + } + return os.OpenFile(path, os.O_RDWR, 0) +}