Skip to content

Commit e10eadb

Browse files
authored
Merge pull request #34 from duplocloud/DUPLO-13771-token-via-jit-redirect
DUPLO-13771 Support duplo-jit redirect flow for JIT token hand-off (Safari Support)
2 parents 2b90aa6 + a68092a commit e10eadb

File tree

9 files changed

+340
-359
lines changed

9 files changed

+340
-359
lines changed

.github/workflows/dev-check.yml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
uses: golangci/golangci-lint-action@v6
2525
with:
2626
only-new-issues: true # Only show new issues for pull requests.
27+
args: --timeout 2m
2728
format:
2829
runs-on: ubuntu-latest
2930
steps:

LICENSE

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ LICENSE
22

33
MIT License
44

5-
Copyright © 2022 DuploCloud, Inc.
5+
Copyright © 2025 DuploCloud, Inc.
66

77
Permission is hereby granted, free of charge, to any person obtaining a copy
88
of this software and associated documentation files (the "Software"), to deal
@@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2020
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2121
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23-
SOFTWARE.
23+
SOFTWARE.

README.md

+38-53
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# duplo-jit
2+
23
Command-line tools for JIT Duplo, AWS and Kubernetes access
34

45
## Installation
@@ -23,15 +24,15 @@ This tool is intended to be used in your `~/.aws/config`. It provides just-in-t
2324

2425
Example `~/.aws/config` for admin access to Duplo:
2526

26-
```
27+
```ini
2728
[profile myduplo-admin]
2829
region=us-west-2
2930
credential_process=duplo-jit aws --admin --host https://MY-DUPLO-HOSTNAME.duplocloud.net --interactive
3031
```
3132

3233
Example `~/.aws/config` for tenant-level access to Duplo:
3334

34-
```
35+
```ini
3536
[profile myduplo-tenant]
3637
region=us-west-2
3738
credential_process=duplo-jit aws --tenant MY-TENANT-NAME --host https://MY-DUPLO-HOSTNAME.duplocloud.net --interactive
@@ -44,89 +45,73 @@ credential_process=duplo-jit aws --tenant MY-TENANT-NAME --host https://MY-DUPLO
4445
```
4546
Usage of duplo-jit:
4647
-admin
47-
Get admin credentials
48+
Get admin credentials
49+
-api-host string
50+
Specify an alternate DuploCloud API base URL if it differs from the UI host (defaults to the value of --host if omitted)
4851
-debug
49-
Turn on verbose (debugging) output
52+
Turn on verbose (debugging) output
5053
-duplo-ops
51-
Get Duplo operations credentials
54+
Get Duplo operations credentials
5255
-host string
53-
Duplo API base URL
56+
DuploCloud base URL
5457
-interactive
55-
Allow getting Duplo credentials via an interactive browser session
56-
-port
57-
Allow choosing a port for the interactive browser session. Default is random
58+
Allow getting Duplo credentials via an interactive browser session
5859
-no-cache
59-
Disable caching (not recommended)
60+
Disable caching (not recommended)
61+
-port int
62+
Port to use for the local web server
6063
-tenant string
61-
Get credentials for the given tenant
64+
Get credentials for the given tenant
6265
-token string
63-
Duplo API token
66+
DuploCloud API token
6467
-version
65-
Output version information and exit
68+
Output version information and exit
6669
```
6770

6871
### duplo-jit duplo --help
6972

7073
```
7174
Usage of duplo-jit:
75+
-api-host string
76+
Specify an alternate DuploCloud API base URL if it differs from the UI host (defaults to the value of --host if omitted)
7277
-debug
73-
Turn on verbose (debugging) output
78+
Turn on verbose (debugging) output
7479
-host string
75-
Duplo API base URL
80+
DuploCloud base URL
7681
-interactive
77-
Allow getting Duplo credentials via an interactive browser session
78-
-port
79-
Allow choosing a port for the interactive browser session. Default is random
82+
Allow getting Duplo credentials via an interactive browser session
8083
-no-cache
81-
Disable caching (not recommended)
84+
Disable caching (not recommended)
85+
-port int
86+
Port to use for the local web server
8287
-token string
83-
Duplo API token
88+
DuploCloud API token
8489
-version
85-
Output version information and exit
90+
Output version information and exit
8691
```
8792

8893
### duplo-jit k8s --help
8994

9095
```
9196
Usage of duplo-jit:
97+
-api-host string
98+
Specify an alternate DuploCloud API base URL if it differs from the UI host (defaults to the value of --host if omitted)
9299
-debug
93-
Turn on verbose (debugging) output
100+
Turn on verbose (debugging) output
94101
-host string
95-
Duplo API base URL
102+
DuploCloud base URL
96103
-interactive
97-
Allow getting Duplo credentials via an interactive browser session
104+
Allow getting Duplo credentials via an interactive browser session
98105
-no-cache
99-
Disable caching (not recommended)
106+
Disable caching (not recommended)
100107
-plan string
101-
Get credentials for the given plan
102-
-tenant string
103-
Get credentials for the given tenant
104-
-token string
105-
Duplo API token
106-
-version
107-
Output version information and exit
108-
```
109-
110-
## Deprecated: duplo-aws-credential-process --help
111-
112-
```
113-
Usage of duplo-aws-credential-process:
114-
-admin
115-
Get admin credentials
116-
-debug
117-
Turn on verbose (debugging) output
118-
-duplo-ops
119-
Get Duplo operations credentials
120-
-host string
121-
Duplo API base URL
122-
-interactive
123-
Allow getting Duplo credentials via an interactive browser session
124-
-no-cache
125-
Disable caching (not recommended)
108+
Get credentials for the given plan
109+
-port int
110+
Port to use for the local web server
126111
-tenant string
127-
Get credentials for the given tenant
112+
Get credentials for the given tenant
128113
-token string
129-
Duplo API token
114+
DuploCloud API token
130115
-version
131-
Output version information and exit
116+
Output version information and exit
132117
```

cmd/duplo-jit/main.go

+42-19
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ func main() {
2626
log.SetOutput(os.Stderr)
2727

2828
// Common command-line arguments.
29-
host := flag.String("host", "", "Duplo API base URL")
30-
token := flag.String("token", "", "Duplo API token")
29+
host := flag.String("host", "", "DuploCloud base URL")
30+
token := flag.String("token", "", "DuploCloud API token")
3131
debug := flag.Bool("debug", false, "Turn on verbose (debugging) output")
3232
noCache := flag.Bool("no-cache", false, "Disable caching (not recommended)")
3333
interactive := flag.Bool("interactive", false, "Allow getting Duplo credentials via an interactive browser session")
3434
port := flag.Int("port", 0, "Port to use for the local web server")
3535
showVersion := flag.Bool("version", false, "Output version information and exit")
36+
apiHost := flag.String("api-host", "", "Specify an alternate DuploCloud API base URL if it differs from the UI host (defaults to the value of --host if omitted)")
3637
admin = new(bool)
3738
duploOps = new(bool)
3839

@@ -76,13 +77,35 @@ func main() {
7677
os.Exit(1)
7778
}
7879

79-
// Refuse to call APIs over anything but https://
80-
// Trim a trailing slash.
81-
if host == nil || !strings.HasPrefix(*host, "https://") {
82-
log.Fatalf("%s: %s", os.Args[0], "--host must be present and start with https://")
80+
// Validate the host.
81+
const fatalFmt = "%s: %s"
82+
if *host == "" {
83+
log.Fatalf(fatalFmt, os.Args[0], "--host must be present")
84+
} else if strings.HasPrefix(*host, "http://localhost") {
85+
fmt.Fprintf(os.Stderr, "Using developer host %s\n", *host)
86+
} else if !strings.HasPrefix(*host, "https://") {
87+
// Refuse to call APIs over anything but https://
88+
log.Fatalf(fatalFmt, os.Args[0], "--host must start with https://")
8389
}
90+
91+
// Trim a trailing slash.
8492
*host = strings.TrimSuffix(*host, "/")
8593

94+
// Validate the api-host if provided.
95+
if *apiHost != "" {
96+
if strings.HasPrefix(*apiHost, "http://localhost") {
97+
fmt.Printf("Using developer api-host %s\n", *apiHost)
98+
} else if !strings.HasPrefix(*apiHost, "https://") {
99+
// Refuse to call APIs over anything but https://
100+
log.Fatalf(fatalFmt, os.Args[0], "--api-host must start with https://")
101+
}
102+
// Trim a trailing slash.
103+
*apiHost = strings.TrimSuffix(*apiHost, "/")
104+
} else {
105+
// By default, the api host is the same as the UI host.
106+
apiHost = host
107+
}
108+
86109
// Possibly enable debugging
87110
if *debug {
88111
duplocloud.LogLevel = duplocloud.TRACE
@@ -92,21 +115,22 @@ func main() {
92115
internal.MustInitCache("duplo-jit", *noCache)
93116

94117
// Get AWS credentials and output them
118+
cacheKey := internal.GetHostCacheKey(*host)
119+
95120
switch cmd {
96121
case "aws":
97122
var creds *internal.AwsConfigOutput
98-
var cacheKey string
99123
if *admin {
100124

101125
// Build the cache key
102-
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "admin"}, ",")
126+
cacheKey = strings.Join([]string{cacheKey, "admin"}, ",")
103127

104128
// Try to find credentials from the cache.
105129
creds = internal.CacheGetAwsConfigOutput(cacheKey)
106130

107131
// Otherwise, get the credentials from Duplo.
108132
if creds == nil {
109-
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
133+
client, _ := internal.MustDuploClient(*host, *apiHost, *token, *interactive, true, *port)
110134
result, err := client.AdminGetJitAwsCredentials()
111135
internal.DieIf(err, "failed to get credentials")
112136
creds = internal.ConvertAwsCreds(result)
@@ -115,14 +139,14 @@ func main() {
115139
} else if *duploOps {
116140

117141
// Build the cache key
118-
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "duplo-ops"}, ",")
142+
cacheKey = strings.Join([]string{cacheKey, "duplo-ops"}, ",")
119143

120144
// Try to find credentials from the cache.
121145
creds = internal.CacheGetAwsConfigOutput(cacheKey)
122146

123147
// Otherwise, get the credentials from Duplo.
124148
if creds == nil {
125-
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
149+
client, _ := internal.MustDuploClient(*host, *apiHost, *token, *interactive, true, *port)
126150
result, err := client.AdminAwsGetJitAccess("duplo-ops")
127151
internal.DieIf(err, "failed to get credentials")
128152
creds = internal.ConvertAwsCreds(result)
@@ -137,11 +161,11 @@ func main() {
137161

138162
// Identify the tenant name to use for the cache key.
139163
var tenantName string
140-
client, _ := internal.MustDuploClient(*host, *token, *interactive, false, *port)
164+
client, _ := internal.MustDuploClient(*host, *apiHost, *token, *interactive, false, *port)
141165
*tenantID, tenantName = getTenantIDAndName(*tenantID, client)
142166

143167
// Build the cache key.
144-
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "tenant", tenantName}, ",")
168+
cacheKey = strings.Join([]string{cacheKey, "tenant", tenantName}, ",")
145169

146170
// Try to find credentials from the cache.
147171
creds = internal.CacheGetAwsConfigOutput(cacheKey)
@@ -159,23 +183,22 @@ func main() {
159183
internal.OutputAwsCreds(creds, cacheKey)
160184

161185
case "duplo":
162-
_, creds := internal.MustDuploClient(*host, *token, *interactive, true, *port)
186+
_, creds := internal.MustDuploClient(*host, *apiHost, *token, *interactive, true, *port)
163187
internal.OutputDuploCreds(creds)
164188

165189
case "k8s":
166190
var creds *clientauthv1beta1.ExecCredential
167-
var cacheKey string
168191
if planID != nil && *planID != "" {
169192

170193
// Build the cache key
171-
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "plan", *planID}, ",")
194+
cacheKey = strings.Join([]string{cacheKey, "plan", *planID}, ",")
172195

173196
// Try to find credentials from the cache.
174197
creds = internal.CacheGetK8sConfigOutput(cacheKey, "")
175198

176199
// Otherwise, get the credentials from Duplo.
177200
if creds == nil {
178-
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
201+
client, _ := internal.MustDuploClient(*host, *apiHost, *token, *interactive, true, *port)
179202
result, err := client.AdminGetK8sJitAccess(*planID)
180203
internal.DieIf(err, "failed to get credentials")
181204
creds = internal.ConvertK8sCreds(result)
@@ -190,11 +213,11 @@ func main() {
190213

191214
// Identify the tenant name to use for the cache key.
192215
var tenantName string
193-
client, _ := internal.MustDuploClient(*host, *token, *interactive, false, *port)
216+
client, _ := internal.MustDuploClient(*host, *apiHost, *token, *interactive, false, *port)
194217
*tenantID, tenantName = getTenantIDAndName(*tenantID, client)
195218

196219
// Build the cache key.
197-
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "tenant", tenantName}, ",")
220+
cacheKey = strings.Join([]string{cacheKey, "tenant", tenantName}, ",")
198221

199222
// Try to find credentials from the cache.
200223
creds = internal.CacheGetK8sConfigOutput(cacheKey, tenantName)

0 commit comments

Comments
 (0)