From 2e9a58239989c57ee55f20d32e42ac62b6f9a4ed Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 11:53:40 +0100 Subject: [PATCH 01/12] added m2m example + readme --- examples/m2m/go.mod | 3 + examples/m2m/main.go | 136 +++++++++++++++++++++++++++++++++++++++++ examples/m2m/readme.md | 69 +++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 examples/m2m/go.mod create mode 100644 examples/m2m/main.go create mode 100644 examples/m2m/readme.md diff --git a/examples/m2m/go.mod b/examples/m2m/go.mod new file mode 100644 index 0000000..faf36da --- /dev/null +++ b/examples/m2m/go.mod @@ -0,0 +1,3 @@ +module example + +go 1.23.4 diff --git a/examples/m2m/main.go b/examples/m2m/main.go new file mode 100644 index 0000000..9a49788 --- /dev/null +++ b/examples/m2m/main.go @@ -0,0 +1,136 @@ +package main + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strings" +) + +// TokenConfig represents the configuration for token retrieval +type TokenConfig struct { + BaseURL string + ClientID string + ServiceAccount string + ServiceToken string + SkipTLSVerify bool +} + +// TokenResponse represents the structure of the token response +type TokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` +} + +// NewTokenConfig creates a configuration from environment variables +func NewTokenConfig() *TokenConfig { + return &TokenConfig{ + BaseURL: getEnv("BASE_URL", "https://localhost:9443"), + ClientID: getEnv("CLIENT_ID", ""), + ServiceAccount: getEnv("SERVICE_ACCOUNT", ""), + ServiceToken: getEnv("SERVICE_TOKEN", ""), + SkipTLSVerify: getBoolEnv("SKIP_TLS_VERIFY", false), + } +} + +// getEnv retrieves an environment variable with a default value +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} + +// getBoolEnv retrieves a boolean environment variable +func getBoolEnv(key string, defaultValue bool) bool { + value := os.Getenv(key) + switch strings.ToLower(value) { + case "true", "1", "yes": + return true + case "false", "0", "no": + return false + default: + return defaultValue + } +} + +// getAccessToken retrieves an access token from the specified endpoint +func getAccessToken(config *TokenConfig) (*TokenResponse, error) { + // Validate required configuration + if config.ClientID == "" || config.ServiceAccount == "" || config.ServiceToken == "" { + return nil, fmt.Errorf("missing required configuration") + } + + // Create a custom HTTP client with optional TLS verification + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: config.SkipTLSVerify}, + } + client := &http.Client{Transport: tr} + + // Prepare the request body + data := url.Values{ + "grant_type": {"client_credentials"}, + "client_id": {config.ClientID}, + "username": {config.ServiceAccount}, + "password": {config.ServiceToken}, + "scope": {"profile"}, + } + + // Create the HTTP request + fullURL := strings.TrimRight(config.BaseURL, "/") + "/application/o/token/" + req, err := http.NewRequest("POST", fullURL, strings.NewReader(data.Encode())) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Send the request + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response: %w", err) + } + + // Check for successful response + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("token retrieval failed (status %d): %s", + resp.StatusCode, string(body)) + } + + // Parse the JSON response + var tokenResp TokenResponse + if err := json.Unmarshal(body, &tokenResp); err != nil { + return nil, fmt.Errorf("error parsing token response: %w\nRaw response: %s", err, string(body)) + } + + return &tokenResp, nil +} + +func main() { + // Load configuration from environment + config := NewTokenConfig() + + // Retrieve access token + tokenResp, err := getAccessToken(config) + if err != nil { + log.Fatalf("Failed to obtain access token: %v", err) + } + + // Print token details + fmt.Println("Access Token:", tokenResp.AccessToken) + fmt.Println("Token Type:", tokenResp.TokenType) + fmt.Println("Expires In:", tokenResp.ExpiresIn, "seconds") +} diff --git a/examples/m2m/readme.md b/examples/m2m/readme.md new file mode 100644 index 0000000..fd70595 --- /dev/null +++ b/examples/m2m/readme.md @@ -0,0 +1,69 @@ + +# Authentik OAuth Token Retrieval Client (M2M) + +This repository provides an example implementation of a Machine-to-Machine (M2M) application using the [Authentik OAuth2 Client Credentials](https://docs.goauthentik.io/docs/add-secure-apps/providers/oauth2/client_credentials) provider. + +## Configuration + +### Environment Variables + +The following environment variables must be set to configure the application: + +- **`BASE_URL`**: + Base URL of the authentication server. + *Default*: `https://localhost:9443` + *Example*: `https://your-auth-server.com` + +- **`CLIENT_ID`**: + OAuth client identifier. + *Required* (no default value). + *Example*: `WxUcBMGZdI7c0e5oYp6mYdEd64acpXSuWKh8zBH5` + +- **`SERVICE_ACCOUNT`**: + Service account username. + *Required* (no default value). + *Example*: `test2test` + +- **`SERVICE_TOKEN`**: + Service account password/token. + *Required* (no default value). + *Example*: `2Dzvbs5O7wjfaUj1k1YSqctRgVA5hDtsi18xIrmKeIn1pV0rn4G5nuuFQUwH` + +- **`SKIP_TLS_VERIFY`**: + Disable TLS certificate verification. + *Default*: `false` + +## Usage + +### Setting Environment Variables + +Set the required environment variables before running the application. For example: + +```bash +export BASE_URL=https://localhost:9443 +export CLIENT_ID=WxUcBMGZdI7c0e5oYp6mYdEd64acpXSuWKh8zBH5 +export SERVICE_ACCOUNT=test2test +export SERVICE_TOKEN=2Dzvbs5O7wjfaUj1k1YSqctRgVA5hDtsi18xIrmKeIn1pV0rn4G5nuuFQUwH +export SKIP_TLS_VERIFY=true + +Running the Application + +Run the application using the following command: + +go run main.go + +Example Output + +When the application is successfully run, you will see output similar to the following: + +Access Token: +Token Type: Bearer +Expires In: 3600 seconds + +The retrieved access token can then be used for authorized access to other resources. + +For more details, refer to the Authentik Documentation. + + +This README ensures clarity and provides copyable Markdown for easy use in your repository. It explicitly mentions that the implementation is for M2M (Machine-to-Machine) applications. + From ee4a93d925be3c0a15e6c22cf48c751a2b9f6547 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 11:55:18 +0100 Subject: [PATCH 02/12] formatting fix --- examples/m2m/readme.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/m2m/readme.md b/examples/m2m/readme.md index fd70595..55cae42 100644 --- a/examples/m2m/readme.md +++ b/examples/m2m/readme.md @@ -45,14 +45,15 @@ export CLIENT_ID=WxUcBMGZdI7c0e5oYp6mYdEd64acpXSuWKh8zBH5 export SERVICE_ACCOUNT=test2test export SERVICE_TOKEN=2Dzvbs5O7wjfaUj1k1YSqctRgVA5hDtsi18xIrmKeIn1pV0rn4G5nuuFQUwH export SKIP_TLS_VERIFY=true +``` -Running the Application +## Running the script -Run the application using the following command: +Run the script using the following command: -go run main.go +`go run main.go` -Example Output +Example Output: When the application is successfully run, you will see output similar to the following: @@ -64,6 +65,4 @@ The retrieved access token can then be used for authorized access to other resou For more details, refer to the Authentik Documentation. - This README ensures clarity and provides copyable Markdown for easy use in your repository. It explicitly mentions that the implementation is for M2M (Machine-to-Machine) applications. - From 86d59dcb3e4bf7e2bd390b87cbb5ff47d3ff8f55 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 11:56:18 +0100 Subject: [PATCH 03/12] formatting fix --- examples/m2m/readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/m2m/readme.md b/examples/m2m/readme.md index 55cae42..fb09a95 100644 --- a/examples/m2m/readme.md +++ b/examples/m2m/readme.md @@ -53,13 +53,15 @@ Run the script using the following command: `go run main.go` -Example Output: +Example Output When the application is successfully run, you will see output similar to the following: +``` Access Token: Token Type: Bearer Expires In: 3600 seconds +``` The retrieved access token can then be used for authorized access to other resources. From 73ef14127de31809208caabe1646e4bfe1a8882a Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 11:57:18 +0100 Subject: [PATCH 04/12] added link to authentik --- examples/m2m/readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/m2m/readme.md b/examples/m2m/readme.md index fb09a95..3c7fdc0 100644 --- a/examples/m2m/readme.md +++ b/examples/m2m/readme.md @@ -65,6 +65,4 @@ Expires In: 3600 seconds The retrieved access token can then be used for authorized access to other resources. -For more details, refer to the Authentik Documentation. - -This README ensures clarity and provides copyable Markdown for easy use in your repository. It explicitly mentions that the implementation is for M2M (Machine-to-Machine) applications. +For more details, refer to the [Authentik](https://goauthentik.io/) Documentation. From 9da47a721902626f6df52c82af62255da34cb2df Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 12:06:34 +0100 Subject: [PATCH 05/12] addressed review --- examples/m2m/main.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/examples/m2m/main.go b/examples/m2m/main.go index 9a49788..d19e57a 100644 --- a/examples/m2m/main.go +++ b/examples/m2m/main.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" ) @@ -30,12 +31,13 @@ type TokenResponse struct { // NewTokenConfig creates a configuration from environment variables func NewTokenConfig() *TokenConfig { + skipTlsVerify, _ := strconv.ParseBool(getEnv("SKIP_TLS_VERIFY", "false")) return &TokenConfig{ BaseURL: getEnv("BASE_URL", "https://localhost:9443"), ClientID: getEnv("CLIENT_ID", ""), ServiceAccount: getEnv("SERVICE_ACCOUNT", ""), ServiceToken: getEnv("SERVICE_TOKEN", ""), - SkipTLSVerify: getBoolEnv("SKIP_TLS_VERIFY", false), + SkipTLSVerify: skipTlsVerify, } } @@ -48,19 +50,6 @@ func getEnv(key, defaultValue string) string { return value } -// getBoolEnv retrieves a boolean environment variable -func getBoolEnv(key string, defaultValue bool) bool { - value := os.Getenv(key) - switch strings.ToLower(value) { - case "true", "1", "yes": - return true - case "false", "0", "no": - return false - default: - return defaultValue - } -} - // getAccessToken retrieves an access token from the specified endpoint func getAccessToken(config *TokenConfig) (*TokenResponse, error) { // Validate required configuration From a57bfece8d90f4d905dd6cb9f0942d56a1743c5a Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 12:19:16 +0100 Subject: [PATCH 06/12] added build for examples --- Makefile | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0a5c56b..c8414aa 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,27 @@ -.PHONY: test +.PHONY: test lint build dev +.DEFAULT_GOAL := dev + +EXAMPLE_DIRS := $(shell find examples -name "go.mod" -exec dirname {} \;) + +define generate_build_target +.PHONY: build-$(1) +build-$(1): + cd $(1) && go build ./... +endef + + +#for each a seperate build target +$(foreach dir,$(EXAMPLE_DIRS),$(eval $(call generate_build_target,$(dir)))) + +# that is called here +build: $(patsubst %,build-%,$(EXAMPLE_DIRS)) + test: go test ./... -v + lint: GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v -.DEFAULT_GOAL := dev + + +dev: + @echo "Development target (customize as needed)" From 527fbfb864bf43b142bbe8d656a14d3ee5b96a4a Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 12:23:21 +0100 Subject: [PATCH 07/12] added build to ci --- .github/workflows/ci.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76bc91f..cb00df5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,8 @@ name: Ci - on: pull_request: branches: [ main, master, development ] - jobs: - lint: name: Lint go code with golangci runs-on: ubuntu-latest @@ -21,7 +18,25 @@ jobs: ls -la make lint shell: bash - + + build: + name: Build project + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + - name: Make repo safe + run: git config --global --add safe.directory /__w/gauth/gauth + - name: Build + run: | + make build + shell: bash test: name: Run ci-tests From 78c9e2d9fc24753b9e4c3d99613540935c5fde48 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 11 Dec 2024 12:26:44 +0100 Subject: [PATCH 08/12] ci version go upgrade --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb00df5..54c3bf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: setup Go uses: actions/setup-go@v5 with: - go-version: '1.22.2' + go-version: '1.23.4' - name: Make repo safe run: git config --global --add safe.directory /__w/gauth/gauth - name: Build @@ -49,7 +49,7 @@ jobs: - name: setup Go uses: actions/setup-go@v5 with: - go-version: '1.22.2' # + go-version: '1.23.4' # - name: Make repo safe run: git config --global --add safe.directory /__w/gauth/gauth - name: Run tests From da94a1ae16f9a3438bd0b3567707a9197c56443e Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Dec 2024 14:28:18 +0100 Subject: [PATCH 09/12] fixed make file according to review --- Makefile | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index c8414aa..6f2bfa8 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,50 @@ -.PHONY: test lint build dev .DEFAULT_GOAL := dev -EXAMPLE_DIRS := $(shell find examples -name "go.mod" -exec dirname {} \;) +.PHONY: build-basic +build-basic: + cd examples/basic && go build ./... -define generate_build_target -.PHONY: build-$(1) -build-$(1): - cd $(1) && go build ./... -endef +.PHONY: build-validation +build-validation: + cd examples/validation && go build ./... +.PHONY: build-m2m +build-m2m: + cd examples/m2m && go build ./... -#for each a seperate build target -$(foreach dir,$(EXAMPLE_DIRS),$(eval $(call generate_build_target,$(dir)))) +.PHONY: test-basic +test-basic: + cd examples/basic && go test ./... -v -# that is called here -build: $(patsubst %,build-%,$(EXAMPLE_DIRS)) +.PHONY: test-validation +test-validation: + cd examples/validation && go test ./... -v -test: - go test ./... -v +.PHONY: test-m2m +test-m2m: + cd examples/m2m && go test ./... -v -lint: - GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v +.PHONY: lint-basic +lint-basic: + cd examples/basic && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v +.PHONY: lint-validation +lint-validation: + cd examples/validation && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v +.PHONY: lint-m2m +lint-m2m: + cd examples/m2m && GOFLAGS=-buildvcs=false golangci-lint run --timeout 5m0s -v + +.PHONY: build-examples +build-examples: build-basic build-validation build-m2m + +.PHONY: test +test: test-basic test-validation test-m2m + +.PHONY: lint +lint: lint-basic lint-validation lint-m2m + +.PHONY: dev dev: @echo "Development target (customize as needed)" From ce01792a7813875858502242cd2a3b6ae6978e61 Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Dec 2024 15:23:30 +0100 Subject: [PATCH 10/12] add examples build to ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54c3bf3..c6fced7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: git config --global --add safe.directory /__w/gauth/gauth - name: Build run: | - make build + make build-examples shell: bash test: From cd4953fae068520300cca76f443596403102f84e Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Dec 2024 15:28:18 +0100 Subject: [PATCH 11/12] fixes linting issue --- examples/basic/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/basic/main.go b/examples/basic/main.go index 3f52026..452eac8 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -45,5 +45,8 @@ func main() { }) }) - router.Run(":8080") + err = router.Run(":8080") + if err != nil { + log.Fatal("Could not start server") + } } From 954daed8cffccb01341faa45fc706b83b2b8a018 Mon Sep 17 00:00:00 2001 From: jp Date: Thu, 12 Dec 2024 15:31:27 +0100 Subject: [PATCH 12/12] addressed linting issue --- examples/validation/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/validation/main.go b/examples/validation/main.go index 09417f7..8f28d82 100644 --- a/examples/validation/main.go +++ b/examples/validation/main.go @@ -47,5 +47,8 @@ func main() { }) }) - router.Run(":8080") + err = router.Run(":8080") + if err != nil { + log.Fatal("Could not start server") + } }