Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tls to gin apiserver #363

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions ui/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
IL_UI_ADMIN_USERNAME=admin
IL_UI_ADMIN_PASSWORD=password
IL_UI_API_SERVER_USERNAME=kitteh
IL_UI_API_SERVER_PASSWORD=floofykittens
IL_UI_API_SERVER_URL=http://<IP>:8000
OAUTH_GITHUB_ID=<OAUTH_APP_ID>
OAUTH_GITHUB_SECRET=<OAUTH_APP_SECRET>
Expand All @@ -12,3 +10,15 @@ IL_GRANITE_MODEL_NAME=<GRANITE_MODEL_NAME>
IL_MERLINITE_API=<MERLINITE_HOST>
IL_MERLINITE_MODEL_NAME=<MERLINITE_MODEL_NAME>
GITHUB_TOKEN=<TOKEN FOR OAUTH INSTRUCTLAB MEMBER LOOKUP>
PRECHECK_ENDPOINT=

# TLS variables
# the following have to absoulte paths
TLS_CLIENT_CERT_PATH=
TLS_CLIENT_KEY_PATH=
TLS_SERVER_CA_CERT_PATH=
# Note, you cannot set TLS_INSECURE in this .env file, you have to pass it to the apiserver as a CLI arg

# API creds variables
API_USER=
API_PASS=
159 changes: 127 additions & 32 deletions ui/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
Expand All @@ -25,8 +27,13 @@
redisQueueArchive = "archived"
)

const PreCheckEndpointURL = "https://merlinite-7b-vllm-openai.apps.fmaas-backend.fmaas.res.ibm.com/v1"
const InstructLabBotUrl = "http://bot:8081"
const (
localEndpoint = "http://localhost:8000/v1"
InstructLabBotUrl = "http://bot:8081"
TLSCertChainPath = "/home/fedora/chain.pem"
TLSClientCRTPath = "/home/fedora/client-tls-crt.pem2"
TLSClientKEYPath = "/home/fedora/client-tls-key.pem2"
)

type ApiServer struct {
router *gin.Engine
Expand All @@ -36,6 +43,7 @@
testMode bool
preCheckEndpointURL string
instructLabBotUrl string
devMode bool
}

type JobData struct {
Expand Down Expand Up @@ -202,25 +210,54 @@
c.JSON(http.StatusCreated, gin.H{"msg": responseBody.String()})
}

// Sent http post request using custom client with zero timeout
func (api *ApiServer) sendPostRequest(url string, body io.Reader) (*http.Response, error) {
client := &http.Client{
Timeout: 0 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
func (api *ApiServer) buildHTTPServer() (http.Client, error) {
tlsInseucre := !api.devMode
if !api.devMode {
certPool := x509.NewCertPool()
pemData, err := os.ReadFile(TLSCertChainPath) // Replace with your certificate file path
if err != nil {
err = fmt.Errorf("Failed to read cert chain file: %s", err)
api.logger.Error(err)
return http.Client{}, err
}
if !certPool.AppendCertsFromPEM(pemData) {
err = fmt.Errorf("Failed to append pemData to certPool: %s", err)
api.logger.Error(err)
return http.Client{}, err
}
tlsConfig := &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: tlsInseucre,
}
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
return *httpClient, nil
} else {
return http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: tlsInseucre},
},
}, nil
}
}

request, err := http.NewRequest("POST", url, body)
// Sent http post request using custom client with zero timeout
func (api *ApiServer) sendPostRequest(url string, body io.Reader) (*http.Response, error) {
client, err := api.buildHTTPServer()
if err != nil {
api.logger.Errorf("Error creating http request: %v", err)
err = fmt.Errorf("Error creating http(s) server: %v", err)
api.logger.Error(err)
return nil, err
}
request.Header.Set("Content-Type", "application/json")
response, err := client.Do(request)
response, err := client.Post(url, "application/json", body)
if err != nil {
api.logger.Errorf("Error sending http request: %v", err)
return nil, err
api.logger.Errorf("Error creating and or sending http request: %v", err)
return response, err
}
return response, nil
}
Expand Down Expand Up @@ -390,26 +427,19 @@
}
endpoint += "models"

http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout = 10 * time.Second
http.DefaultTransport.(*http.Transport).ExpectContinueTimeout = 1 * time.Second
client, err := api.buildHTTPServer()

req, err := http.NewRequestWithContext(api.ctx, "GET", endpoint, nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
response, err := client.Get(endpoint)
if err != nil {
return "", fmt.Errorf("failed to fetch model details: %w", err)
}
defer resp.Body.Close()
defer response.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
if response.StatusCode != http.StatusOK {
return "", fmt.Errorf("unexpected status code: %d", response.StatusCode)
}

body, err := io.ReadAll(resp.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %w", err)
}
Expand Down Expand Up @@ -459,10 +489,51 @@
redisAddress := pflag.String("redis-server", "localhost:6379", "Redis server address")
apiUser := pflag.String("api-user", "", "API username")
apiPass := pflag.String("api-pass", "", "API password")
preCheckEndpointURL := pflag.String("precheck-endpoint", PreCheckEndpointURL, "Precheck endpoint URL")
preCheckEndpointURL := pflag.String("precheck-endpoint", "", "Precheck endpoint URL")
InstructLabBotUrl := pflag.String("bot-url", InstructLabBotUrl, "InstructLab Bot URL")
// TLS variables
devMode := pflag.Bool("dev-mode", false, "Whether to skip TLS verification")
pflag.Parse()

/* ENV support, most variabls take 3 options, with the following priority:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let's take these as an input parameter only. We should probably move to urfave/cli? It does pretty clean job with these 3 options. Irrespective of that, I think it would be good if we take care of this (checking env variable) across all the go binaries on the repo in separate PR? wdyt?

1) flag
2) env
3) acceptable defaults
*/

// NOTE: not all variables support all 3 methods, in which case they will be documented via comments.
// With no comment, assume they support all 3.

// Precheck endpoint
if *preCheckEndpointURL == "" {
preCheckEndpointURLEnvValue := os.Getenv("PECHECK_ENDPOINT")
if preCheckEndpointURLEnvValue != "" {
*preCheckEndpointURL = preCheckEndpointURLEnvValue
} else {
*preCheckEndpointURL = localEndpoint
}
}

// NOTE: TLSInsecure not settable by env, just apiserver cli flag or defaults to false

/* API credentials
API creds support only apiserver cli flag or env, no default values.
*/
// API user
if *apiUser == "" {
apiUserEnvValue := os.Getenv("API_USER")
if apiUserEnvValue != "" {
*apiUser = apiUserEnvValue
}
}
// API pass
if *apiPass == "" {
apiPassEnvValue := os.Getenv("API_PASS")
if apiPassEnvValue != "" {
*apiPass = apiPassEnvValue
}
}

logger := setupLogger(*debugFlag)
defer logger.Sync()

Expand All @@ -474,6 +545,7 @@
Addr: *redisAddress,
})

tlsInsecure := !*devMode
router := gin.Default()
svr := ApiServer{
router: router,
Expand All @@ -483,11 +555,34 @@
testMode: *testMode,
preCheckEndpointURL: *preCheckEndpointURL,
instructLabBotUrl: *InstructLabBotUrl,
devMode: *devMode,
}
svr.setupRoutes(*apiUser, *apiPass)

svr.logger.Info("ApiServer starting", zap.String("listen-address", *listenAddress))
if err := svr.router.Run(*listenAddress); err != nil {
svr.logger.Error("ApiServer failed to start", zap.Error(err))
if tlsInsecure == false {
// Check if we is valid key pair

certPool := x509.NewCertPool()
pemData, err := os.ReadFile(TLSCertChainPath) // Replace with your certificate file path
if err != nil {
log.Fatalf("Failed to read cert chain file: %s", err)
}
if !certPool.AppendCertsFromPEM(pemData) {
log.Fatalf("Failed to append pemData to certPool: %s", err)
}
// tlsConfig := &tls.Config{
// RootCAs: certPool,
// InsecureSkipVerify: *tlsInsecure,
// }
// if err := svr.router.
svr.logger.Info("ApiServer starting with TLS", zap.String("listen-address", *listenAddress))
if err := svr.router.RunTLS(*listenAddress, *TLSCertChainPath, nil); != nil {

Check failure on line 579 in ui/apiserver/apiserver.go

View workflow job for this annotation

GitHub Actions / gofmt

expected operand, found '!='
// if err := svr.router.RunTLS(*listenAddress, *tlsClientCertPath, *tlsClientKeyPath); err != nil {
svr.logger.Error("ApiServer failed to start", zap.Error(err))
}
} else {
if err := svr.router.Run(*listenAddress); err != nil {

Check failure on line 584 in ui/apiserver/apiserver.go

View workflow job for this annotation

GitHub Actions / gofmt

expected '{', found 'if'
svr.logger.Error("ApiServer failed to start", zap.Error(err))

Check failure on line 585 in ui/apiserver/apiserver.go

View workflow job for this annotation

GitHub Actions / gofmt

missing ',' before newline in composite literal
}
}
}

Check failure on line 588 in ui/apiserver/apiserver.go

View workflow job for this annotation

GitHub Actions / gofmt

expected '}', found 'EOF'
2 changes: 2 additions & 0 deletions ui/compose.ui
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ services:
network_mode: "host"
depends_on:
- redis
env_file:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you also need to fix the deploy/compose/deplo

- .env
environment:
# Bind on all interface
LISTEN_ADDRESS: "${LISTEN_ADDRESS:-:3000}"
Expand Down
Loading