Skip to content

Commit b78cdf1

Browse files
authored
Add support for EMBootkit and native golang IO mode (#655)
1 parent 49d5357 commit b78cdf1

File tree

19 files changed

+1617
-660
lines changed

19 files changed

+1617
-660
lines changed

device-discovery-agent/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/open-edge-platform/infra-onboarding/onboarding-manager v1.39.10
1010
github.com/sirupsen/logrus v1.9.4
1111
golang.org/x/oauth2 v0.36.0
12+
golang.org/x/term v0.40.0
1213
google.golang.org/grpc v1.81.0-dev
1314
)
1415

device-discovery-agent/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
4242
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
4343
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
4444
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
45+
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
46+
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
4547
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
4648
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
4749
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=

device-discovery-agent/internal/auth/auth.go

Lines changed: 15 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -4,180 +4,31 @@
44
package auth
55

66
import (
7-
"bytes"
8-
"crypto/tls"
9-
"crypto/x509"
10-
"encoding/json"
7+
"context"
118
"fmt"
12-
"io"
13-
"net/http"
14-
"net/url"
15-
"os"
169
"strings"
1710
)
1811

19-
// loadCACertPool loads the CA certificate from a file and returns a certificate pool.
20-
func loadCACertPool(caCertPath string) (*x509.CertPool, error) {
21-
caCert, err := os.ReadFile(caCertPath)
22-
if err != nil {
23-
return nil, fmt.Errorf("failed to read CA certificate: %w", err)
24-
}
25-
caCertPool := x509.NewCertPool()
26-
if !caCertPool.AppendCertsFromPEM(caCert) {
27-
return nil, fmt.Errorf("failed to append CA certificate")
28-
}
29-
return caCertPool, nil
30-
}
31-
32-
func fetchAccessToken(keycloakURL string, clientID string, clientSecret string, caCertPath string) (string, error) {
33-
// Prepare the request data
34-
data := url.Values{}
35-
data.Set("grant_type", "client_credentials")
36-
data.Set("client_id", clientID)
37-
data.Set("client_secret", clientSecret)
38-
reqBody := bytes.NewBufferString(data.Encode())
39-
40-
// Create the HTTP request
41-
req, err := http.NewRequest("POST", "https://"+keycloakURL, reqBody)
42-
if err != nil {
43-
return "", err
44-
}
45-
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
46-
47-
// Configure TLS
48-
var tlsConfig *tls.Config
49-
if caCertPath != "" {
50-
// Load the CA certificate from provided path
51-
caCertPool, err := loadCACertPool(caCertPath)
52-
if err != nil {
53-
return "", fmt.Errorf("error loading CA certificate: %v", err)
54-
}
55-
tlsConfig = &tls.Config{
56-
RootCAs: caCertPool,
57-
MinVersion: tls.VersionTLS12,
58-
}
59-
} else {
60-
// Use system default CA certificates
61-
tlsConfig = &tls.Config{
62-
MinVersion: tls.VersionTLS12,
63-
}
64-
}
65-
66-
// Create an HTTP client with TLS configuration
67-
client := &http.Client{
68-
Transport: &http.Transport{
69-
TLSClientConfig: tlsConfig,
70-
},
71-
}
72-
73-
// Perform the request
74-
resp, err := client.Do(req)
75-
if err != nil {
76-
return "", err
77-
}
78-
defer resp.Body.Close()
79-
80-
// Check for a successful response
81-
if resp.StatusCode != http.StatusOK {
82-
return "", fmt.Errorf("failed to get access token, status: %s", resp.Status)
83-
}
84-
85-
// Parse the JSON response
86-
var result map[string]interface{}
87-
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
88-
return "", err
89-
}
90-
91-
// Extract the access token
92-
token, ok := result["access_token"].(string)
93-
if !ok || token == "" {
94-
return "", fmt.Errorf("access token not found in response")
95-
}
96-
97-
return token, nil
98-
}
99-
100-
func fetchReleaseToken(releaseServerURL string, accessToken string, caCertPath string) (string, error) {
101-
// Ensure the access token is not empty
102-
if accessToken == "" {
103-
return "", fmt.Errorf("access token is required")
104-
}
105-
106-
// Configure TLS
107-
var tlsConfig *tls.Config
108-
if caCertPath != "" {
109-
// Load CA certificate from provided path
110-
caCertPool, err := loadCACertPool(caCertPath)
111-
if err != nil {
112-
return "", fmt.Errorf("error loading CA certificate: %v", err)
113-
}
114-
tlsConfig = &tls.Config{
115-
RootCAs: caCertPool,
116-
MinVersion: tls.VersionTLS12,
117-
}
118-
} else {
119-
// Use system default CA certificates
120-
tlsConfig = &tls.Config{
121-
MinVersion: tls.VersionTLS12,
122-
}
123-
}
124-
125-
// Construct the HTTP request
126-
req, err := http.NewRequest("GET", "https://"+releaseServerURL, nil)
127-
if err != nil {
128-
return "", fmt.Errorf("error creating request: %v", err)
129-
}
130-
131-
// Add the authorization header with the bearer token
132-
req.Header.Set("Authorization", "Bearer "+accessToken)
133-
134-
// Create an HTTP client with TLS configuration
135-
client := &http.Client{
136-
Transport: &http.Transport{
137-
TLSClientConfig: tlsConfig,
138-
},
139-
}
140-
141-
// Send the request
142-
resp, err := client.Do(req)
143-
if err != nil {
144-
return "", fmt.Errorf("error sending request: %v", err)
145-
}
146-
defer resp.Body.Close()
147-
148-
// Check if the response status is 200 OK
149-
if resp.StatusCode != http.StatusOK {
150-
return "", fmt.Errorf("failed to get release token, status: %s", resp.Status)
151-
}
152-
153-
// Read the response body
154-
body, err := io.ReadAll(resp.Body)
155-
if err != nil {
156-
return "", fmt.Errorf("error reading response body: %v", err)
157-
}
158-
159-
// Convert the response body to a string (the token)
160-
token := string(body)
161-
162-
// Validate the received token
163-
if token == "null" || token == "" {
164-
return "", fmt.Errorf("invalid token received")
165-
}
166-
167-
return token, nil
168-
}
169-
170-
// ClientAuth handles authentication and retrieves tokens.
12+
// ClientAuth handles authentication and retrieves tokens using client credentials.
13+
// This function is used in non-interactive mode after the device has been onboarded.
17114
func ClientAuth(clientID string, clientSecret string, keycloakURL string, accessTokenURL string, releaseTokenURL string, caCertPath string) (idpAccessToken string, releaseToken string, err error) {
172-
// Fetch JWT access token from Keycloak
173-
idpAccessToken, err = fetchAccessToken(keycloakURL+accessTokenURL, clientID, clientSecret, caCertPath)
15+
ctx := context.Background()
16+
17+
// Fetch JWT access token from Keycloak using client_credentials flow
18+
idpAccessToken, err = FetchClientCredentialsToken(ctx, ClientCredentialsParams{
19+
KeycloakURL: keycloakURL,
20+
TokenPath: accessTokenURL,
21+
ClientID: clientID,
22+
ClientSecret: clientSecret,
23+
CACertPath: caCertPath,
24+
})
17425
if err != nil {
17526
return "", "", fmt.Errorf("failed to get JWT access token from Keycloak: %v", err)
17627
}
17728

17829
// Fetch release service token
179-
releaseTokenURL = strings.Replace(keycloakURL, "keycloak", "release", 1) + releaseTokenURL
180-
releaseToken, err = fetchReleaseToken(releaseTokenURL, idpAccessToken, caCertPath)
30+
releaseURL := strings.Replace(keycloakURL, "keycloak", "release", 1) + releaseTokenURL
31+
releaseToken, err = FetchReleaseToken(ctx, releaseURL, idpAccessToken, caCertPath)
18132
if err != nil {
18233
return "", "", fmt.Errorf("failed to get release service token: %v", err)
18334
}

device-discovery-agent/internal/auth/auth_fuzz_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,18 @@ BQADQQBvZmZzZXQgdGVzdCBkYXRhIGZvciBmdXp6aW5nIHB1cnBvc2VzIG9ubHk=
4646
t.Skip("Failed to write test file")
4747
}
4848

49-
// Test loadCACertPool - should not panic
50-
pool, err := loadCACertPool(certPath)
49+
// Test LoadCACertPool - should not panic
50+
pool, err := LoadCACertPool(certPath)
5151

5252
// Validate results
5353
if err == nil {
5454
// If no error, pool should be valid and non-nil
5555
if pool == nil {
56-
t.Error("loadCACertPool returned nil pool without error")
56+
t.Error("LoadCACertPool returned nil pool without error")
5757
}
5858
// Verify it's a valid x509.CertPool
5959
if _, ok := interface{}(pool).(*x509.CertPool); !ok {
60-
t.Error("loadCACertPool returned invalid type")
60+
t.Error("LoadCACertPool returned invalid type")
6161
}
6262
}
6363
// If error occurred, that's fine - we just don't want panics
@@ -84,7 +84,7 @@ func FuzzJSONTokenResponse(f *testing.F) {
8484
f.Add([]byte(`{"access_token":"` + strings.Repeat("x", 10000) + `"}`)) // Very long token
8585

8686
f.Fuzz(func(t *testing.T, jsonData []byte) {
87-
// Simulate the JSON parsing logic from fetchAccessToken
87+
// Simulate the JSON parsing logic from FetchClientCredentialsToken
8888
var result map[string]interface{}
8989
err := json.Unmarshal(jsonData, &result)
9090

@@ -125,7 +125,7 @@ func FuzzReleaseTokenResponse(f *testing.F) {
125125
f.Add([]byte("{\"token\":\"value\"}")) // JSON when expecting plain text
126126

127127
f.Fuzz(func(t *testing.T, tokenData []byte) {
128-
// Simulate the token validation logic from fetchReleaseToken
128+
// Simulate the token validation logic from FetchReleaseToken
129129
token := string(tokenData)
130130

131131
// Test the validation logic
@@ -170,7 +170,7 @@ func FuzzURLConstruction(f *testing.F) {
170170
fullURL := "https://" + urlStr
171171

172172
// Validate URL parsing doesn't panic
173-
// This simulates the URL construction in fetchAccessToken and fetchReleaseToken
173+
// This simulates the URL construction in FetchClientCredentialsToken and FetchReleaseToken
174174
_ = fullURL
175175

176176
// Test strings.Replace operation from ClientAuth
@@ -199,7 +199,7 @@ func FuzzAuthorizationHeader(f *testing.F) {
199199
f.Add(strings.Repeat("a", 10000)) // Very long token
200200

201201
f.Fuzz(func(t *testing.T, token string) {
202-
// Simulate Authorization header construction from fetchReleaseToken
202+
// Simulate Authorization header construction from FetchReleaseToken
203203
authHeader := "Bearer " + token
204204

205205
// Check for potential header injection vulnerabilities

0 commit comments

Comments
 (0)