Skip to content

Commit 237cff6

Browse files
authored
Added TLS support to be used with Switcher API (#45)
1 parent 5a99196 commit 237cff6

File tree

7 files changed

+92
-25
lines changed

7 files changed

+92
-25
lines changed

.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ MONGO_DB=switcher-gitops-test
55
GIT_TOKEN_PRIVATE_KEY=SecretSecretSecretSecretSecretSe
66
HANDLER_WAITING_TIME=1m
77

8+
SWITCHER_API_CA_CERT=
89
SWITCHER_API_URL=https://switcherapi.com/api
910
SWITCHER_API_JWT_SECRET=SecretSecretSecretSecretSecretSe
1011
SWITCHER_PATH_GRAPHQL=/gitops-graphql

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ build:
22
go build -o ./bin/app ./src/cmd/app/main.go
33

44
run:
5-
GOOS=windows $env:GO_ENV="test"; go run ./src/cmd/app/main.go
6-
GOOS=linux GO_ENV=test go run ./src/cmd/app/main.go
5+
ifeq ($(OS),Windows_NT)
6+
$env:GO_ENV="test"; go run ./src/cmd/app/main.go
7+
else
8+
GO_ENV=test go run ./src/cmd/app/main.go
9+
endif
710

811
test:
912
go test -p 1 -coverpkg=./... -v

resources/fixtures/api/dummy.pem

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-----BEGIN CERTIFICATE-----
2+
-----END CERTIFICATE-----

src/core/api.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ package core
22

33
import (
44
"bytes"
5+
"crypto/tls"
6+
"crypto/x509"
57
"encoding/json"
68
"errors"
79
"fmt"
810
"io"
911
"net/http"
12+
"os"
1013
"time"
1114

1215
"github.com/golang-jwt/jwt"
1316
"github.com/switcherapi/switcher-gitops/src/config"
1417
"github.com/switcherapi/switcher-gitops/src/model"
18+
"github.com/switcherapi/switcher-gitops/src/utils"
1519
)
1620

1721
type GraphQLRequest struct {
@@ -32,14 +36,16 @@ type IAPIService interface {
3236
}
3337

3438
type ApiService struct {
35-
apiKey string
36-
apiUrl string
39+
apiKey string
40+
apiUrl string
41+
caCertPath string
3742
}
3843

39-
func NewApiService(apiKey string, apiUrl string) *ApiService {
44+
func NewApiService(apiKey string, apiUrl string, caCertPath string) *ApiService {
4045
return &ApiService{
41-
apiKey: apiKey,
42-
apiUrl: apiUrl,
46+
apiKey: apiKey,
47+
apiUrl: apiUrl,
48+
caCertPath: caCertPath,
4349
}
4450
}
4551

@@ -101,8 +107,7 @@ func (a *ApiService) doGraphQLRequest(domainId string, query string) (string, er
101107
setHeaders(req, token)
102108

103109
// Send the request
104-
client := &http.Client{}
105-
resp, err := client.Do(req)
110+
resp, err := a.doRequest(req)
106111
if err != nil {
107112
return "", err
108113
}
@@ -123,8 +128,7 @@ func (a *ApiService) doPostRequest(url string, domainId string, body []byte) (st
123128
setHeaders(req, token)
124129

125130
// Send the request
126-
client := &http.Client{}
127-
resp, err := client.Do(req)
131+
resp, err := a.doRequest(req)
128132
if err != nil {
129133
return "", 0, err
130134
}
@@ -134,6 +138,35 @@ func (a *ApiService) doPostRequest(url string, domainId string, body []byte) (st
134138
return string(responseBody), resp.StatusCode, nil
135139
}
136140

141+
func (a *ApiService) doRequest(req *http.Request) (*http.Response, error) {
142+
var client *http.Client
143+
144+
if a.caCertPath != "" {
145+
caCert, err := os.ReadFile(a.caCertPath)
146+
147+
if err != nil {
148+
utils.LogError("Error reading CA certificate file: " + err.Error())
149+
return nil, err
150+
}
151+
152+
caCertPool := x509.NewCertPool()
153+
caCertPool.AppendCertsFromPEM([]byte(caCert))
154+
155+
utils.LogDebug("Using CA certificate for HTTPS requests")
156+
client = &http.Client{
157+
Transport: &http.Transport{
158+
TLSClientConfig: &tls.Config{
159+
RootCAs: caCertPool,
160+
},
161+
},
162+
}
163+
} else {
164+
client = &http.Client{}
165+
}
166+
167+
return client.Do(req)
168+
}
169+
137170
func generateBearerToken(apiKey string, subject string) string {
138171
// Define the claims for the JWT token
139172
claims := jwt.MapClaims{

src/core/api_test.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestFetchSnapshotVersion(t *testing.T) {
1919
fakeApiServer := givenApiResponse(http.StatusOK, responsePayload)
2020
defer fakeApiServer.Close()
2121

22-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
22+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
2323
version, _ := apiService.FetchSnapshotVersion("domainId", "default")
2424

2525
assert.Contains(t, version, "version", "Missing version in response")
@@ -30,7 +30,7 @@ func TestFetchSnapshotVersion(t *testing.T) {
3030
fakeApiServer := givenApiResponse(http.StatusUnauthorized, `{ "error": "Invalid API token" }`)
3131
defer fakeApiServer.Close()
3232

33-
apiService := NewApiService("INVALID_KEY", fakeApiServer.URL)
33+
apiService := NewApiService("INVALID_KEY", fakeApiServer.URL, "")
3434
version, _ := apiService.FetchSnapshotVersion("domainId", "default")
3535

3636
assert.Contains(t, version, "Invalid API token")
@@ -41,14 +41,14 @@ func TestFetchSnapshotVersion(t *testing.T) {
4141
fakeApiServer := givenApiResponse(http.StatusUnauthorized, responsePayload)
4242
defer fakeApiServer.Close()
4343

44-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
44+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
4545
version, _ := apiService.FetchSnapshotVersion("INVALID_DOMAIN", "default")
4646

4747
assert.Contains(t, version, "errors")
4848
})
4949

5050
t.Run("Should return error - invalid API URL", func(t *testing.T) {
51-
apiService := NewApiService(config.GetEnv(SWITCHER_API_JWT_SECRET), "http://localhost:8080")
51+
apiService := NewApiService(config.GetEnv(SWITCHER_API_JWT_SECRET), "http://localhost:8080", "")
5252
_, err := apiService.FetchSnapshotVersion("domainId", "default")
5353

5454
assert.NotNil(t, err)
@@ -61,7 +61,7 @@ func TestFetchSnapshot(t *testing.T) {
6161
fakeApiServer := givenApiResponse(http.StatusOK, responsePayload)
6262
defer fakeApiServer.Close()
6363

64-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
64+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
6565
snapshot, _ := apiService.FetchSnapshot("domainId", "default")
6666

6767
assert.Contains(t, snapshot, "domain", "Missing domain in snapshot")
@@ -75,7 +75,7 @@ func TestFetchSnapshot(t *testing.T) {
7575
fakeApiServer := givenApiResponse(http.StatusOK, responsePayload)
7676
defer fakeApiServer.Close()
7777

78-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
78+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
7979
snapshot, _ := apiService.FetchSnapshot("domainId", "default")
8080
data := apiService.NewDataFromJson([]byte(snapshot))
8181

@@ -88,7 +88,7 @@ func TestFetchSnapshot(t *testing.T) {
8888
fakeApiServer := givenApiResponse(http.StatusUnauthorized, `{ "error": "Invalid API token" }`)
8989
defer fakeApiServer.Close()
9090

91-
apiService := NewApiService("INVALID_KEY", fakeApiServer.URL)
91+
apiService := NewApiService("INVALID_KEY", fakeApiServer.URL, "")
9292
snapshot, _ := apiService.FetchSnapshot("domainId", "default")
9393

9494
assert.Contains(t, snapshot, "Invalid API token")
@@ -99,14 +99,14 @@ func TestFetchSnapshot(t *testing.T) {
9999
fakeApiServer := givenApiResponse(http.StatusUnauthorized, responsePayload)
100100
defer fakeApiServer.Close()
101101

102-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
102+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
103103
snapshot, _ := apiService.FetchSnapshot("INVALID_DOMAIN", "default")
104104

105105
assert.Contains(t, snapshot, "errors")
106106
})
107107

108108
t.Run("Should return error - invalid API URL", func(t *testing.T) {
109-
apiService := NewApiService(config.GetEnv(SWITCHER_API_JWT_SECRET), "http://localhost:8080")
109+
apiService := NewApiService(config.GetEnv(SWITCHER_API_JWT_SECRET), "http://localhost:8080", "")
110110
_, err := apiService.FetchSnapshot("domainId", "default")
111111

112112
assert.NotNil(t, err)
@@ -123,7 +123,7 @@ func TestPushChangesToAPI(t *testing.T) {
123123
}`)
124124
defer fakeApiServer.Close()
125125

126-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
126+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
127127

128128
// Test
129129
response, _ := apiService.PushChanges("domainId", diff)
@@ -140,7 +140,7 @@ func TestPushChangesToAPI(t *testing.T) {
140140
fakeApiServer := givenApiResponse(http.StatusBadRequest, `{ "error": "Config already exists" }`)
141141
defer fakeApiServer.Close()
142142

143-
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL)
143+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "")
144144

145145
// Test
146146
_, err := apiService.PushChanges("domainId", diff)
@@ -157,7 +157,7 @@ func TestPushChangesToAPI(t *testing.T) {
157157
fakeApiServer := givenApiResponse(http.StatusUnauthorized, `{ "error": "Invalid API token" }`)
158158
defer fakeApiServer.Close()
159159

160-
apiService := NewApiService("[INVALID_KEY]", fakeApiServer.URL)
160+
apiService := NewApiService("[INVALID_KEY]", fakeApiServer.URL, "")
161161

162162
// Test
163163
_, err := apiService.PushChanges("domainId", diff)
@@ -170,7 +170,7 @@ func TestPushChangesToAPI(t *testing.T) {
170170
t.Run("Should return error - API not accessible", func(t *testing.T) {
171171
// Given
172172
diff := givenDiffResult("default")
173-
apiService := NewApiService("[SWITCHER_API_JWT_SECRET]", "http://localhost:8080")
173+
apiService := NewApiService("[SWITCHER_API_JWT_SECRET]", "http://localhost:8080", "")
174174

175175
// Test
176176
_, err := apiService.PushChanges("domainId", diff)
@@ -180,6 +180,33 @@ func TestPushChangesToAPI(t *testing.T) {
180180
})
181181
}
182182

183+
func TestFetchSnapshotWithCaCert(t *testing.T) {
184+
t.Run("Should return snapshot", func(t *testing.T) {
185+
responsePayload := utils.ReadJsonFromFile("../../resources/fixtures/api/default_snapshot.json")
186+
fakeApiServer := givenApiResponse(http.StatusOK, responsePayload)
187+
defer fakeApiServer.Close()
188+
189+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "../../resources/fixtures/api/dummy.pem")
190+
snapshot, _ := apiService.FetchSnapshot("domainId", "default")
191+
192+
assert.Contains(t, snapshot, "domain", "Missing domain in snapshot")
193+
assert.Contains(t, snapshot, "version", "Missing version in snapshot")
194+
assert.Contains(t, snapshot, "group", "Missing groups in snapshot")
195+
assert.Contains(t, snapshot, "config", "Missing config in snapshot")
196+
})
197+
198+
t.Run("Should return error - certificate not found", func(t *testing.T) {
199+
responsePayload := utils.ReadJsonFromFile("../../resources/fixtures/api/default_snapshot.json")
200+
fakeApiServer := givenApiResponse(http.StatusOK, responsePayload)
201+
defer fakeApiServer.Close()
202+
203+
apiService := NewApiService(SWITCHER_API_JWT_SECRET, fakeApiServer.URL, "invalid.pem")
204+
_, err := apiService.FetchSnapshot("domainId", "default")
205+
206+
assert.NotNil(t, err)
207+
})
208+
}
209+
183210
// Helpers
184211

185212
func givenDiffResult(environment string) model.DiffResult {

src/core/core_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func setup() {
2828
mongoDb = db.InitDb()
2929

3030
accountRepository := repository.NewAccountRepositoryMongo(mongoDb)
31-
apiService := NewApiService("apiKey", "")
31+
apiService := NewApiService("apiKey", "", "")
3232
comparatorService := NewComparatorService()
3333
coreHandler = NewCoreHandler(accountRepository, apiService, comparatorService)
3434
}

src/server/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func initCoreHandler(db *mongo.Database) *core.CoreHandler {
7979
apiService := core.NewApiService(
8080
config.GetEnv("SWITCHER_API_JWT_SECRET"),
8181
config.GetEnv("SWITCHER_API_URL"),
82+
config.GetEnv("SWITCHER_API_CA_CERT"),
8283
)
8384

8485
coreHandler := core.NewCoreHandler(accountRepository, apiService, comparatorService)

0 commit comments

Comments
 (0)