Skip to content

Commit 545f5d6

Browse files
committed
refactor: enhance validation for authenticating with console
1 parent aa7853b commit 545f5d6

5 files changed

Lines changed: 209 additions & 7 deletions

File tree

internal/cli/cli.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package cli
77

88
import (
9+
"os"
910
"strings"
1011
"time"
1112

@@ -23,6 +24,7 @@ import (
2324
const (
2425
amtInitializeMaxAttempts = 4
2526
amtInitializeBackoff = 2 * time.Second
27+
configFilePath = "config.yaml"
2628
)
2729

2830
var sleepForAMTRetry = time.Sleep
@@ -99,7 +101,7 @@ func Parse(args []string, amtCommand amt.Interface) (*kong.Context, *CLI, error)
99101
kong.UsageOnError(),
100102
kong.DefaultEnvars("RPC"),
101103
kong.ConfigureHelp(helpOpts),
102-
kong.Configuration(kongyaml.Loader, "config.yaml"),
104+
kong.Configuration(kongyaml.Loader, configFilePath),
103105
kong.BindToProvider(func() amt.Interface { return amtCommand }),
104106
}
105107

@@ -117,6 +119,12 @@ func Parse(args []string, amtCommand amt.Interface) (*kong.Context, *CLI, error)
117119
}
118120

119121
ctx, perr := parser.Parse(parseArgs)
122+
123+
// Log config file presence after parsing (logging is configured by AfterApply at this point)
124+
if _, statErr := os.Stat(configFilePath); statErr == nil {
125+
log.Infof("Using configuration file: %s (flag values may originate from this file)", configFilePath)
126+
}
127+
120128
if perr == nil {
121129
return ctx, &cli, nil
122130
}

internal/commands/activate/activate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ func (cmd *ActivateCmd) runHttpProfileFullflow(ctx *commands.Context) error {
307307

308308
cfg, err := fetcher.FetchProfile()
309309
if err != nil {
310-
return fmt.Errorf("failed to fetch profile: %w", err)
310+
return err
311311
}
312312

313313
// Resolve AMT/MEBx/MPS passwords — generate random ones when the profile requests it.

internal/commands/auth.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,32 @@ import (
1313
"github.com/sirupsen/logrus"
1414
)
1515

16-
// ServerAuthFlags provides common auth options for server communications
17-
// Either AuthToken (Bearer) OR both AuthUsername and AuthPassword (Basic) should be supplied.
16+
// ServerAuthFlags provides common auth options for server communications.
17+
// When AuthEndpoint is set, either AuthToken (Bearer) OR both AuthUsername
18+
// and AuthPassword (Basic) must be supplied.
1819
type ServerAuthFlags struct {
1920
AuthToken string `help:"Bearer token for server authentication" name:"auth-token" env:"AUTH_TOKEN"`
2021
AuthUsername string `help:"Username for basic auth (used when no token)" name:"auth-username" env:"AUTH_USERNAME"`
2122
AuthPassword string `help:"Password for basic auth (used when no token)" name:"auth-password" env:"AUTH_PASSWORD"`
22-
// Optional endpoint for exchanging credentials for a token (primarily used when fetching HTTP profiles)
23-
AuthEndpoint string `help:"The endpoint to call to fetch a token. Assumes the same host as the profile URL unless an absolute URL is provided; defaults to the Console path /api/v1/authorize." name:"auth-endpoint" default:"/api/v1/authorize"`
23+
AuthEndpoint string `help:"Token exchange endpoint. Requires --auth-token or --auth-username/--auth-password. Resolved relative to the profile URL host unless absolute." name:"auth-endpoint" env:"AUTH_ENDPOINT"`
24+
}
25+
26+
// Validate implements kong.Validatable. When auth-endpoint is set,
27+
// either auth-token or (auth-username + auth-password) is required.
28+
func (a *ServerAuthFlags) Validate() error {
29+
if a.AuthEndpoint == "" {
30+
return nil
31+
}
32+
33+
if a.AuthToken != "" {
34+
return nil
35+
}
36+
37+
if a.AuthUsername != "" && a.AuthPassword != "" {
38+
return nil
39+
}
40+
41+
return fmt.Errorf("--auth-endpoint requires --auth-token or both --auth-username and --auth-password")
2442
}
2543

2644
// ValidateRequired enforces that some form of auth is present when required.

internal/commands/auth_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2024
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
package commands
7+
8+
import (
9+
"net/http"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestServerAuthFlags_Validate(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
flags ServerAuthFlags
20+
wantErr string
21+
}{
22+
{
23+
name: "no endpoint — no validation",
24+
flags: ServerAuthFlags{},
25+
},
26+
{
27+
name: "endpoint with token — ok",
28+
flags: ServerAuthFlags{
29+
AuthEndpoint: "/api/v1/authorize",
30+
AuthToken: "tok",
31+
},
32+
},
33+
{
34+
name: "endpoint with username and password — ok",
35+
flags: ServerAuthFlags{
36+
AuthEndpoint: "/api/v1/authorize",
37+
AuthUsername: "user",
38+
AuthPassword: "pass",
39+
},
40+
},
41+
{
42+
name: "endpoint with no credentials — error",
43+
flags: ServerAuthFlags{
44+
AuthEndpoint: "/api/v1/authorize",
45+
},
46+
wantErr: "--auth-endpoint requires --auth-token or both --auth-username and --auth-password",
47+
},
48+
{
49+
name: "endpoint with username only — error",
50+
flags: ServerAuthFlags{
51+
AuthEndpoint: "/api/v1/authorize",
52+
AuthUsername: "user",
53+
},
54+
wantErr: "--auth-endpoint requires --auth-token or both --auth-username and --auth-password",
55+
},
56+
{
57+
name: "endpoint with password only — error",
58+
flags: ServerAuthFlags{
59+
AuthEndpoint: "/api/v1/authorize",
60+
AuthPassword: "pass",
61+
},
62+
wantErr: "--auth-endpoint requires --auth-token or both --auth-username and --auth-password",
63+
},
64+
{
65+
name: "endpoint with token and username/password — token wins, ok",
66+
flags: ServerAuthFlags{
67+
AuthEndpoint: "/api/v1/authorize",
68+
AuthToken: "tok",
69+
AuthUsername: "user",
70+
AuthPassword: "pass",
71+
},
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
err := tt.flags.Validate()
78+
if tt.wantErr != "" {
79+
require.Error(t, err)
80+
assert.Contains(t, err.Error(), tt.wantErr)
81+
} else {
82+
assert.NoError(t, err)
83+
}
84+
})
85+
}
86+
}
87+
88+
func TestServerAuthFlags_ValidateRequired(t *testing.T) {
89+
tests := []struct {
90+
name string
91+
flags ServerAuthFlags
92+
required bool
93+
wantErr bool
94+
}{
95+
{
96+
name: "not required — always ok",
97+
flags: ServerAuthFlags{},
98+
required: false,
99+
},
100+
{
101+
name: "required with token",
102+
flags: ServerAuthFlags{AuthToken: "tok"},
103+
required: true,
104+
},
105+
{
106+
name: "required with username and password",
107+
flags: ServerAuthFlags{AuthUsername: "user", AuthPassword: "pass"},
108+
required: true,
109+
},
110+
{
111+
name: "required with nothing",
112+
flags: ServerAuthFlags{},
113+
required: true,
114+
wantErr: true,
115+
},
116+
{
117+
name: "required with username only",
118+
flags: ServerAuthFlags{AuthUsername: "user"},
119+
required: true,
120+
wantErr: true,
121+
},
122+
}
123+
124+
for _, tt := range tests {
125+
t.Run(tt.name, func(t *testing.T) {
126+
err := tt.flags.ValidateRequired(tt.required)
127+
if tt.wantErr {
128+
assert.Error(t, err)
129+
} else {
130+
assert.NoError(t, err)
131+
}
132+
})
133+
}
134+
}
135+
136+
func TestServerAuthFlags_ApplyToRequest(t *testing.T) {
137+
tests := []struct {
138+
name string
139+
flags ServerAuthFlags
140+
wantHeader string
141+
}{
142+
{
143+
name: "token sets bearer",
144+
flags: ServerAuthFlags{AuthToken: "my-token"},
145+
wantHeader: "Bearer my-token",
146+
},
147+
{
148+
name: "username/password sets basic",
149+
flags: ServerAuthFlags{AuthUsername: "user", AuthPassword: "pass"},
150+
wantHeader: "Basic dXNlcjpwYXNz",
151+
},
152+
{
153+
name: "token takes precedence over basic",
154+
flags: ServerAuthFlags{AuthToken: "tok", AuthUsername: "user", AuthPassword: "pass"},
155+
wantHeader: "Bearer tok",
156+
},
157+
{
158+
name: "no credentials — no header",
159+
flags: ServerAuthFlags{},
160+
wantHeader: "",
161+
},
162+
}
163+
164+
for _, tt := range tests {
165+
t.Run(tt.name, func(t *testing.T) {
166+
req, _ := http.NewRequest("GET", "http://example.com", nil)
167+
tt.flags.ApplyToRequest(req)
168+
169+
assert.Equal(t, tt.wantHeader, req.Header.Get("Authorization"))
170+
})
171+
}
172+
}

internal/profile/fetcher.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ func (f *ProfileFetcher) fetchData(u, token string) ([]byte, error) {
198198
defer resp.Body.Close()
199199

200200
if resp.StatusCode == http.StatusUnauthorized {
201-
return nil, fmt.Errorf("unauthorized: authentication required or token invalid")
201+
if token == "" {
202+
return nil, fmt.Errorf("unauthorized: no credentials provided — use --auth-token or --auth-username/--auth-password")
203+
}
204+
205+
return nil, fmt.Errorf("unauthorized: server rejected the bearer token")
202206
}
203207

204208
if resp.StatusCode != http.StatusOK {

0 commit comments

Comments
 (0)