-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "Fetcher" tools for OAuth2 token retrieval
- Add utility to obtain OAuth 2 token via Client Credentials flow - Add utility to read OAuth 2 token from file - README updates - add coverage for new tools - misc fixes for previous tooling - Add automatic retry functionality for OAuth2 token retrieval step used by list-emails CLI app and OAuth2-based monitoring plugin - Add winres config to xoauth2 tool (and to new "Fetcher" tools) refs GH-318 refs GH-319
- Loading branch information
Showing
24 changed files
with
903 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright 2022 Adam Chalkley | ||
// | ||
// https://github.com/atc0005/check-mail | ||
// | ||
// Licensed under the MIT License. See LICENSE file in the project root for | ||
// full license information. | ||
|
||
// Small CLI app used to fetch an OAuth2 Client Credentials token. The intent | ||
// is to provide a tool that allows retrieving a token via a cron job and | ||
// caching it for later use. Optionally, the token can be used immediately | ||
// from a shell script. | ||
// | ||
// See our [GitHub repo]: | ||
// | ||
// - to review documentation (including examples) | ||
// - for the latest code | ||
// - to file an issue or submit improvements for review and potential | ||
// inclusion into the project | ||
// | ||
// [GitHub repo]: https://github.com/atc0005/check-mail | ||
package main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright 2022 Adam Chalkley | ||
// | ||
// https://github.com/atc0005/check-mail | ||
// | ||
// Licensed under the MIT License. See LICENSE file in the project root for | ||
// full license information. | ||
|
||
//go:generate go-winres make --product-version=git-tag --file-version=git-tag | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/atc0005/check-mail/internal/config" | ||
"github.com/atc0005/check-mail/internal/oauth2" | ||
"github.com/rs/zerolog" | ||
) | ||
|
||
func main() { | ||
|
||
ctx := context.Background() | ||
|
||
// Setup configuration by parsing user-provided flags | ||
cfg, cfgErr := config.New(config.AppType{FetcherOAuth2TokenFromAuthServer: true}) | ||
switch { | ||
case errors.Is(cfgErr, config.ErrVersionRequested): | ||
fmt.Println(config.Version()) | ||
|
||
return | ||
|
||
case errors.Is(cfgErr, config.ErrHelpRequested): | ||
fmt.Println(cfg.Help()) | ||
|
||
return | ||
|
||
case cfgErr != nil: | ||
|
||
// We make some assumptions when setting up our logger as we do not | ||
// have a working configuration based on sysadmin-specified choices. | ||
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr} | ||
logger := zerolog.New(consoleWriter).With().Timestamp().Caller().Logger() | ||
|
||
logger.Err(cfgErr).Msg("Error initializing application") | ||
|
||
return | ||
} | ||
|
||
var logger zerolog.Logger | ||
switch { | ||
case cfg.FetcherOAuth2TokenSettings.Filename != "": | ||
logger = cfg.Log.With(). | ||
Str("filename", cfg.FetcherOAuth2TokenSettings.Filename). | ||
Logger() | ||
default: | ||
logger = cfg.Log.With().Logger() | ||
} | ||
|
||
logger.Debug().Msg("Application configuration initialized") | ||
|
||
logger.Debug().Msg("Fetching Client Credentials token") | ||
token, err := oauth2.GetClientCredentialsToken( | ||
ctx, | ||
cfg.FetcherOAuth2TokenSettings.ClientID, | ||
cfg.FetcherOAuth2TokenSettings.ClientSecret, | ||
cfg.FetcherOAuth2TokenSettings.Scopes, | ||
cfg.FetcherOAuth2TokenSettings.TokenURL, | ||
cfg.RetrievalAttempts(), | ||
) | ||
if err != nil { | ||
logger.Error().Err(err).Msg("Failed to retrieve token") | ||
os.Exit(1) | ||
} | ||
logger.Debug(). | ||
Str("token_expiration", token.Expiry.Format(time.RFC3339)). | ||
Str("token_type", token.Type()). | ||
Msg("Token retrieved") | ||
|
||
var data []byte | ||
var emittedAsJSON bool | ||
switch { | ||
|
||
case cfg.FetcherOAuth2TokenSettings.EmitTokenAsJSON: | ||
var err error | ||
data, err = json.MarshalIndent(token, "", "\t") | ||
if err != nil { | ||
logger.Error(). | ||
Err(err). | ||
Msg("Failed to marshal token to JSON format") | ||
os.Exit(1) | ||
} | ||
logger.Debug().Msg("Successfully converted token to JSON") | ||
|
||
emittedAsJSON = true | ||
|
||
default: | ||
logger.Debug().Msg("Retaining access token as plaintext value") | ||
data = []byte(token.AccessToken) | ||
emittedAsJSON = false | ||
} | ||
|
||
switch { | ||
case cfg.FetcherOAuth2TokenSettings.Filename != "": | ||
err := os.WriteFile(filepath.Clean(cfg.FetcherOAuth2TokenSettings.Filename), data, 0600) | ||
if err != nil { | ||
logger.Error(). | ||
Err(err). | ||
Msg("Failed to write data to output file") | ||
os.Exit(1) | ||
} | ||
|
||
logger.Debug().Msg("Successfully wrote data to file") | ||
|
||
default: | ||
n, err := os.Stdout.Write(data) | ||
if err != nil { | ||
logger.Error().Err(err).Msg("Failed to write data to stdout") | ||
os.Exit(1) | ||
} | ||
logger.Debug(). | ||
Int("bytes_written", n). | ||
Bool("emitted_as_json", emittedAsJSON). | ||
Msg("Emitted retrieved token") | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
{ | ||
"RT_MANIFEST": { | ||
"#1": { | ||
"0409": { | ||
"identity": { | ||
"name": "", | ||
"version": "" | ||
}, | ||
"description": "Small CLI tool to fetch OAuth2 Client Credentials token from specified token URL", | ||
"minimum-os": "win7", | ||
"execution-level": "as invoker", | ||
"ui-access": false, | ||
"auto-elevate": false, | ||
"dpi-awareness": "system", | ||
"disable-theming": false, | ||
"disable-window-filtering": false, | ||
"high-resolution-scrolling-aware": false, | ||
"ultra-high-resolution-scrolling-aware": false, | ||
"long-path-aware": false, | ||
"printer-driver-isolation": false, | ||
"gdi-scaling": false, | ||
"segment-heap": false, | ||
"use-common-controls-v6": false | ||
} | ||
} | ||
}, | ||
"RT_VERSION": { | ||
"#1": { | ||
"0000": { | ||
"fixed": { | ||
"file_version": "0.0.0.0", | ||
"product_version": "0.0.0.0" | ||
}, | ||
"info": { | ||
"0409": { | ||
"Comments": "Part of the atc0005/check-mail project", | ||
"CompanyName": "github.com/atc0005", | ||
"FileDescription": "Small CLI tool to fetch OAuth2 Client Credentials token from specified token URL", | ||
"FileVersion": "", | ||
"InternalName": "fetch-token", | ||
"LegalCopyright": "© Adam Chalkley. Licensed under MIT.", | ||
"LegalTrademarks": "", | ||
"OriginalFilename": "main.go", | ||
"PrivateBuild": "", | ||
"ProductName": "check-mail", | ||
"ProductVersion": "", | ||
"SpecialBuild": "" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright 2022 Adam Chalkley | ||
// | ||
// https://github.com/atc0005/check-mail | ||
// | ||
// Licensed under the MIT License. See LICENSE file in the project root for | ||
// full license information. | ||
|
||
// Small CLI app used to read an OAuth2 Client Credentials token from a file | ||
// for use within a shell script. A separate tool is used to retrieve the | ||
// token from an authority (e.g., via a cron job) and cache it for this tool | ||
// to read. | ||
// | ||
// See our [GitHub repo]: | ||
// | ||
// - to review documentation (including examples) | ||
// - for the latest code | ||
// - to file an issue or submit improvements for review and potential | ||
// inclusion into the project | ||
// | ||
// [GitHub repo]: https://github.com/atc0005/check-mail | ||
package main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2022 Adam Chalkley | ||
// | ||
// https://github.com/atc0005/check-mail | ||
// | ||
// Licensed under the MIT License. See LICENSE file in the project root for | ||
// full license information. | ||
|
||
//go:generate go-winres make --product-version=git-tag --file-version=git-tag | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/atc0005/check-mail/internal/config" | ||
"github.com/rs/zerolog" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
func main() { | ||
|
||
// Setup configuration by parsing user-provided flags | ||
cfg, cfgErr := config.New(config.AppType{FetcherOAuth2TokenFromCache: true}) | ||
switch { | ||
case errors.Is(cfgErr, config.ErrVersionRequested): | ||
fmt.Println(config.Version()) | ||
|
||
return | ||
|
||
case errors.Is(cfgErr, config.ErrHelpRequested): | ||
fmt.Println(cfg.Help()) | ||
|
||
return | ||
|
||
case cfgErr != nil: | ||
|
||
// We make some assumptions when setting up our logger as we do not | ||
// have a working configuration based on sysadmin-specified choices. | ||
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr} | ||
logger := zerolog.New(consoleWriter).With().Timestamp().Caller().Logger() | ||
|
||
logger.Err(cfgErr).Msg("Error initializing application") | ||
|
||
return | ||
} | ||
|
||
logger := cfg.Log.With(). | ||
Str("filename", cfg.FetcherOAuth2TokenSettings.Filename). | ||
Logger() | ||
|
||
logger.Debug().Msg("Application configuration initialized") | ||
|
||
logger.Debug().Msg("Fetching Client Credentials token from file") | ||
data, err := os.ReadFile(cfg.FetcherOAuth2TokenSettings.Filename) | ||
if err != nil { | ||
logger.Error().Err(err).Msg("Failed to read file contents") | ||
os.Exit(1) | ||
} | ||
logger.Debug().Msg("Successfully read file contents") | ||
|
||
var output []byte | ||
switch { | ||
case bytes.Contains(data, []byte("{")): | ||
logger.Error().Err(err).Msg("File contents appear to be JSON, will attempt to parse as JSON") | ||
|
||
var token oauth2.Token | ||
if err := json.Unmarshal(data, &token); err != nil { | ||
logger.Error().Err(err).Msg("Failed to parse file contents as JSON") | ||
os.Exit(1) | ||
} | ||
logger.Debug().Msg("Successfully parsed file contents as JSON") | ||
|
||
if !token.Valid() { | ||
logger.Error(). | ||
Str("token_expiration", token.Expiry.Format(time.RFC3339)). | ||
Str("token_type", token.Type()). | ||
Msg("Token is NOT valid; a new token should be retrieved and cached in file") | ||
os.Exit(1) | ||
} | ||
|
||
logger.Debug(). | ||
Str("token_expiration", token.Expiry.Format(time.RFC3339)). | ||
Str("token_type", token.Type()). | ||
Msg("Token is valid, retrieving access token value") | ||
|
||
output = []byte(token.AccessToken) | ||
|
||
default: | ||
logger.Debug().Msg("File contents do not appear to be JSON") | ||
logger.Debug().Msg("Attempting to parse file contents as plaintext access token") | ||
output = data | ||
} | ||
|
||
n, err := os.Stdout.Write(output) | ||
if err != nil { | ||
logger.Error().Err(err).Msg("Failed to emit token") | ||
os.Exit(1) | ||
} | ||
logger.Debug(). | ||
Int("bytes_written", n). | ||
Msg("Emitted retrieved token") | ||
|
||
} |
Oops, something went wrong.