Add TLS to the UI
1. Add the libraries needed to do arbitrary https 2. Update the main protocol to HTTPS and WSS 3. Moved the UI conf file to the etc dir in the tree Fixes 54 Change-Id: I142366f053e73fb413291af458c8b5dcb9ab388a
This commit is contained in:
parent
c3a0184f6b
commit
a155654a44
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,6 +11,10 @@ dist
|
|||||||
/build
|
/build
|
||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
|
# local conf files
|
||||||
|
etc/*.pem
|
||||||
|
etc/*.json
|
||||||
|
|
||||||
# Only exists if Bazel was run
|
# Only exists if Bazel was run
|
||||||
/bazel-out
|
/bazel-out
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export class WebsocketService implements OnDestroy {
|
|||||||
this.ws.close();
|
this.ws.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ws = new WebSocket('ws://localhost:8080/ws');
|
this.ws = new WebSocket('wss://localhost:10443/ws');
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
this.ws.onmessage = (event) => {
|
||||||
this.messageHandler(WebsocketService.messageToObject(event.data));
|
this.messageHandler(WebsocketService.messageToObject(event.data));
|
||||||
|
15
etc/airshipui.json.example
Executable file
15
etc/airshipui.json.example
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"webservice": {
|
||||||
|
"host": "<host>",
|
||||||
|
"port": <port>,
|
||||||
|
"publicKey": "<path>/<cert.pem>",
|
||||||
|
"privateKey": "<path>/<key.pem>"
|
||||||
|
},
|
||||||
|
"dashboards": [
|
||||||
|
{
|
||||||
|
"name": "Dash",
|
||||||
|
"baseURL": "https://<FQDN>",
|
||||||
|
"path": "/<example>/<path>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -17,7 +17,6 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -38,11 +37,22 @@ var rootCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
// Add a 'version' command, in addition to the '--version' option that is auto created
|
// Add a 'version' command, in addition to the '--version' option that is auto created
|
||||||
rootCmd.AddCommand(newVersionCmd())
|
rootCmd.AddCommand(newVersionCmd())
|
||||||
|
|
||||||
|
// Add the config file Flag
|
||||||
|
rootCmd.Flags().StringVarP(
|
||||||
|
&configs.UIConfigFile,
|
||||||
|
"conf",
|
||||||
|
"c",
|
||||||
|
"etc/airshipui.json",
|
||||||
|
"This will set the location of the conf file needed to start the UI",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add the logging level flag
|
||||||
rootCmd.Flags().IntVar(
|
rootCmd.Flags().IntVar(
|
||||||
&log.LogLevel,
|
&log.LogLevel,
|
||||||
"loglevel",
|
"loglevel",
|
||||||
6,
|
6,
|
||||||
"This well set the log level, anything at or below that level will be viewed, all others suppressed\n"+
|
"This will set the log level, anything at or below that level will be viewed, all others suppressed\n"+
|
||||||
" 6 -- Trace\n"+
|
" 6 -- Trace\n"+
|
||||||
" 5 -- Debug\n"+
|
" 5 -- Debug\n"+
|
||||||
" 4 -- Info\n"+
|
" 4 -- Info\n"+
|
||||||
@ -53,16 +63,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func launch(cmd *cobra.Command, args []string) {
|
func launch(cmd *cobra.Command, args []string) {
|
||||||
// set default config path
|
|
||||||
// TODO: do we want to make this a flag that can be passed in?
|
|
||||||
airshipUIConfigPath, err := getDefaultConfigPath()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error setting config path %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read AirshipUI config file
|
// Read AirshipUI config file
|
||||||
if err := configs.SetUIConfig(airshipUIConfigPath); err != nil {
|
if err := configs.SetUIConfig(); err != nil {
|
||||||
log.Errorf("config %s", err)
|
log.Fatalf("config %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// start webservice and listen for the the ctl + c to exit
|
// start webservice and listen for the the ctl + c to exit
|
||||||
@ -83,12 +86,3 @@ func Execute() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConfigPath() (string, error) {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.FromSlash(home + "/.airship/airshipui.json"), nil
|
|
||||||
}
|
|
||||||
|
@ -15,20 +15,28 @@
|
|||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/config"
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipui/pkg/cryptography"
|
||||||
|
"opendev.org/airship/airshipui/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// variables related to UI config
|
// variables related to UI config
|
||||||
var (
|
var (
|
||||||
UIConfig Config
|
UIConfig Config
|
||||||
|
UIConfigFile string
|
||||||
|
etcDir *string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config basic structure to hold configuration params for Airship UI
|
// Config basic structure to hold configuration params for Airship UI
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
WebService *WebService `json:"webservice,omitempty"`
|
||||||
AuthMethod *AuthMethod `json:"authMethod,omitempty"`
|
AuthMethod *AuthMethod `json:"authMethod,omitempty"`
|
||||||
Dashboards []Dashboard `json:"dashboards,omitempty"`
|
Dashboards []Dashboard `json:"dashboards,omitempty"`
|
||||||
}
|
}
|
||||||
@ -40,6 +48,14 @@ type AuthMethod struct {
|
|||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebService describes the things we need to know to start the web container
|
||||||
|
type WebService struct {
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
PublicKey string `json:"publicKey,omitempty"`
|
||||||
|
PrivateKey string `json:"privateKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard structure
|
// Dashboard structure
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
@ -111,11 +127,11 @@ type WsMessage struct {
|
|||||||
|
|
||||||
// SetUIConfig sets the UIConfig object with values obtained from
|
// SetUIConfig sets the UIConfig object with values obtained from
|
||||||
// airshipui.json, located at 'filename'
|
// airshipui.json, located at 'filename'
|
||||||
// TODO: add watcher to the json file to reload conf on change
|
// TODO: add watcher to the json file to reload conf on change (maybe not needed)
|
||||||
func SetUIConfig(filename string) error {
|
func SetUIConfig() error {
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(UIConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return checkConfigs()
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@ -129,5 +145,101 @@ func SetUIConfig(filename string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return checkConfigs()
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConfigs() error {
|
||||||
|
if UIConfig.WebService == nil {
|
||||||
|
log.Debug("No UI config found, generating ssl keys & host & port info")
|
||||||
|
err := setEtcDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyFile := filepath.Join(*etcDir, "key.pem")
|
||||||
|
publicKeyFile := filepath.Join(*etcDir, "cert.pem")
|
||||||
|
|
||||||
|
err = writeTestSSL(privateKeyFile, publicKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
UIConfig.WebService = &WebService{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 10443,
|
||||||
|
PublicKey: publicKeyFile,
|
||||||
|
PrivateKey: privateKeyFile,
|
||||||
|
}
|
||||||
|
err = cryptography.TestCertValidity(publicKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(UIConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(UIConfigFile, bytes, 0440)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTestSSL(privateKeyFile string, publicKeyFile string) error {
|
||||||
|
// get and write out private key
|
||||||
|
log.Warnf("Generating private key %s. DO NOT USE THIS FOR PRODUCTION", privateKeyFile)
|
||||||
|
privateKey, err := getAndWritePrivateKey(privateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get and write out public key
|
||||||
|
log.Warnf("Generating public key %s. DO NOT USE THIS FOR PRODUCTION", publicKeyFile)
|
||||||
|
err = getAndWritePublicKey(publicKeyFile, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAndWritePrivateKey(fileName string) (*rsa.PrivateKey, error) {
|
||||||
|
privateKeyBytes, privateKey, err := cryptography.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(fileName, privateKeyBytes, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAndWritePublicKey(fileName string, privateKey *rsa.PrivateKey) error {
|
||||||
|
publicKeyBytes, err := cryptography.GeneratePublicKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(fileName, publicKeyBytes, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setEtcDir() error {
|
||||||
|
if etcDir == nil {
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dir, err = filepath.Abs(filepath.Join(path.Dir(dir), "etc"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
etcDir = &dir
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
package configs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fakeFile string = "/fake/config/path"
|
|
||||||
testFile string = "testdata/airshipui.json"
|
|
||||||
invalidTestFile string = "testdata/airshipui_invalid.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DummyDashboardsConfig returns an array of populated Dashboard structs
|
|
||||||
func dummyDashboardsConfig() []Dashboard {
|
|
||||||
return []Dashboard{
|
|
||||||
{
|
|
||||||
Name: "dummy_dashboard",
|
|
||||||
BaseURL: "http://dummyhost",
|
|
||||||
Path: "fake/login/path",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dummyAuthMethodConfig() *AuthMethod {
|
|
||||||
return &AuthMethod{
|
|
||||||
URL: "http://fake.auth.method.com/auth",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetUIConfig(t *testing.T) {
|
|
||||||
conf := Config{
|
|
||||||
Dashboards: dummyDashboardsConfig(),
|
|
||||||
AuthMethod: dummyAuthMethodConfig(),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SetUIConfig(testFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, conf, UIConfig)
|
|
||||||
|
|
||||||
err = SetUIConfig(invalidTestFile)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileNotFound(t *testing.T) {
|
|
||||||
err := SetUIConfig(fakeFile)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
12
pkg/configs/testdata/airshipui.json
vendored
12
pkg/configs/testdata/airshipui.json
vendored
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"authMethod": {
|
|
||||||
"url": "http://fake.auth.method.com/auth"
|
|
||||||
},
|
|
||||||
"dashboards": [
|
|
||||||
{
|
|
||||||
"name": "dummy_dashboard",
|
|
||||||
"baseURL": "http://dummyhost",
|
|
||||||
"path": "fake/login/path"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
6
pkg/configs/testdata/airshipui_invalid.json
vendored
6
pkg/configs/testdata/airshipui_invalid.json
vendored
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"authMethod": {
|
|
||||||
"url": "http://fake.auth.method.com/auth"
|
|
||||||
},
|
|
||||||
"dashboards": [],
|
|
||||||
}
|
|
73
pkg/configs/testdata/config.yaml
vendored
73
pkg/configs/testdata/config.yaml
vendored
@ -1,73 +0,0 @@
|
|||||||
apiVersion: airshipit.org/v1alpha1
|
|
||||||
bootstrapInfo:
|
|
||||||
default:
|
|
||||||
builder:
|
|
||||||
networkConfigFileName: network-config
|
|
||||||
outputMetadataFileName: output-metadata.yaml
|
|
||||||
userDataFileName: user-data
|
|
||||||
container:
|
|
||||||
containerRuntime: docker
|
|
||||||
image: quay.io/airshipit/isogen:latest-debian_stable
|
|
||||||
volume: /srv/iso:/config
|
|
||||||
remoteDirect:
|
|
||||||
isoUrl: http://localhost:8099/debian-custom.iso
|
|
||||||
dummy_bootstrap_config:
|
|
||||||
builder:
|
|
||||||
networkConfigFileName: netconfig
|
|
||||||
outputMetadataFileName: output-metadata.yaml
|
|
||||||
userDataFileName: user-data
|
|
||||||
container:
|
|
||||||
containerRuntime: docker
|
|
||||||
image: dummy_image:dummy_tag
|
|
||||||
volume: /dummy:dummy
|
|
||||||
clusters:
|
|
||||||
kubernetes:
|
|
||||||
clusterType:
|
|
||||||
target:
|
|
||||||
bootstrapInfo: default
|
|
||||||
clusterKubeconf: kubernetes_target
|
|
||||||
managementConfiguration: default
|
|
||||||
contexts:
|
|
||||||
admin@kubernetes:
|
|
||||||
contextKubeconf: kubernetes_target
|
|
||||||
currentContext: admin@kubernetes
|
|
||||||
kind: Config
|
|
||||||
managementConfiguration:
|
|
||||||
default:
|
|
||||||
insecure: true
|
|
||||||
systemActionRetries: 30
|
|
||||||
systemRebootDelay: 30
|
|
||||||
type: redfish
|
|
||||||
dummy_management_config:
|
|
||||||
insecure: true
|
|
||||||
type: redfish
|
|
||||||
manifests:
|
|
||||||
default:
|
|
||||||
primaryRepositoryName: primary
|
|
||||||
repositories:
|
|
||||||
primary:
|
|
||||||
checkout:
|
|
||||||
branch: master
|
|
||||||
commitHash: ""
|
|
||||||
force: false
|
|
||||||
tag: ""
|
|
||||||
url: https://opendev.org/airship/treasuremap
|
|
||||||
subPath: treasuremap/manifests/site
|
|
||||||
targetPath: /tmp/default
|
|
||||||
dummy_manifest:
|
|
||||||
primaryRepositoryName: primary
|
|
||||||
repositories:
|
|
||||||
primary:
|
|
||||||
auth:
|
|
||||||
sshKey: testdata/test-key.pem
|
|
||||||
type: ssh-key
|
|
||||||
checkout:
|
|
||||||
branch: ""
|
|
||||||
commitHash: ""
|
|
||||||
force: false
|
|
||||||
tag: v1.0.1
|
|
||||||
url: http://dummy.url.com/manifests.git
|
|
||||||
subPath: manifests/site/test-site
|
|
||||||
targetPath: /var/tmp/
|
|
||||||
users:
|
|
||||||
admin: {}
|
|
19
pkg/configs/testdata/kubeconfig.yaml
vendored
19
pkg/configs/testdata/kubeconfig.yaml
vendored
@ -1,19 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
clusters:
|
|
||||||
- cluster:
|
|
||||||
certificate-authority: pki/cluster-ca.pem
|
|
||||||
server: https://10.0.0.1:6553
|
|
||||||
name: kubernetes_target
|
|
||||||
contexts:
|
|
||||||
- context:
|
|
||||||
cluster: kubernetes_target
|
|
||||||
user: admin
|
|
||||||
name: admin@kubernetes
|
|
||||||
current-context: admin@kubernetes
|
|
||||||
kind: Config
|
|
||||||
preferences: {}
|
|
||||||
users:
|
|
||||||
- name: admin
|
|
||||||
user:
|
|
||||||
client-certificate: pki/admin.pem
|
|
||||||
client-key: pki/admin-key.pem
|
|
110
pkg/cryptography/cryptography.go
Executable file
110
pkg/cryptography/cryptography.go
Executable file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cryptography
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipui/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keySize = 4096 // 4k key
|
||||||
|
)
|
||||||
|
|
||||||
|
// GeneratePrivateKey will a pem encoded private key and an rsa private key object
|
||||||
|
func GeneratePrivateKey() ([]byte, *rsa.PrivateKey, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Problem generating private key", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err = pem.Encode(buf, &pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Problem generating private key pem", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePublicKey will create a pem encoded cert
|
||||||
|
func GeneratePublicKey(privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||||
|
template := generateCSR()
|
||||||
|
derCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err = pem.Encode(buf, &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: derCert,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCSR creates the base information needed to create the certificate
|
||||||
|
func generateCSR() x509.Certificate {
|
||||||
|
return x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "localhost",
|
||||||
|
Organization: []string{"Airship UI"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCertValidity will check if the cert defined in the conf is not past its not after date
|
||||||
|
func TestCertValidity(pemFile string) error {
|
||||||
|
r, err := ioutil.ReadFile(pemFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(r)
|
||||||
|
_, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the validity of the cert
|
||||||
|
// TODO: Add a cert check for time based validity here
|
||||||
|
// fmt.Println(cert.NotAfter)
|
||||||
|
return nil
|
||||||
|
}
|
42
pkg/webservice/server_test.go → pkg/cryptography/cryptography_test.go
Normal file → Executable file
42
pkg/webservice/server_test.go → pkg/cryptography/cryptography_test.go
Normal file → Executable file
@ -12,31 +12,35 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package webservice
|
package cryptography
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func TestGeneratePrivateKey(t *testing.T) {
|
||||||
serverAddr string = "localhost:8080"
|
pem, key, err := GeneratePrivateKey()
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
go WebServer()
|
|
||||||
// wait for the webserver to come up
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRootURI(t *testing.T) {
|
|
||||||
resp, err := http.Get("http://" + serverAddr)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
require.NotNil(t, key)
|
||||||
// this will be not found because of where the webservice starts
|
require.NotNil(t, pem)
|
||||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
}
|
||||||
|
|
||||||
|
func TestGeneratePublicKey(t *testing.T) {
|
||||||
|
_, privateKey, err := GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cert, err := GeneratePublicKey(privateKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTestCertValidity(t *testing.T) {
|
||||||
|
_, privateKey, err := GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cert, err := GeneratePublicKey(privateKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cert)
|
||||||
}
|
}
|
@ -16,6 +16,7 @@ package webservice
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"opendev.org/airship/airshipui/pkg/configs"
|
"opendev.org/airship/airshipui/pkg/configs"
|
||||||
@ -85,7 +86,11 @@ func WebServer() {
|
|||||||
// start proxies for web based use
|
// start proxies for web based use
|
||||||
startProxies()
|
startProxies()
|
||||||
|
|
||||||
// TODO: pull ports out into conf files
|
// Calculate the address and start on the host and port specified in the config
|
||||||
log.Info("Attempting to start webservice on localhost:8080")
|
addr := configs.UIConfig.WebService.Host + ":" + strconv.Itoa(configs.UIConfig.WebService.Port)
|
||||||
log.Fatal(http.ListenAndServe(":8080", webServerMux))
|
log.Infof("Attempting to start webservice on %s", addr)
|
||||||
|
log.Fatal(http.ListenAndServeTLS(addr,
|
||||||
|
configs.UIConfig.WebService.PublicKey,
|
||||||
|
configs.UIConfig.WebService.PrivateKey,
|
||||||
|
webServerMux))
|
||||||
}
|
}
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package webservice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"opendev.org/airship/airshipui/pkg/configs"
|
|
||||||
"opendev.org/airship/airshipui/pkg/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// client messages
|
|
||||||
keepalive string = `{"type":"ui","component":"keepalive"}`
|
|
||||||
unknownType string = `{"type":"fake_type","component":"initialize"}`
|
|
||||||
unknownComponent string = `{"type":"ui","component":"fake_component"}`
|
|
||||||
)
|
|
||||||
|
|
||||||
var client *websocket.Conn
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
u := url.URL{Scheme: "ws", Host: serverAddr, Path: "/ws"}
|
|
||||||
var err error
|
|
||||||
client, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
// get server response to "initialize" message from client which is sent by default
|
|
||||||
var response configs.WsMessage
|
|
||||||
err = client.ReadJSON(&response)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeepalive(t *testing.T) {
|
|
||||||
// get server response to "keepalive" message from client
|
|
||||||
response, err := getResponse(keepalive)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := configs.WsMessage{
|
|
||||||
SessionID: response.SessionID,
|
|
||||||
Type: configs.UI,
|
|
||||||
Component: configs.Keepalive,
|
|
||||||
// don't fail on timestamp diff
|
|
||||||
Timestamp: response.Timestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, expected, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnknownType(t *testing.T) {
|
|
||||||
response, err := getResponse(unknownType)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := configs.WsMessage{
|
|
||||||
SessionID: response.SessionID,
|
|
||||||
Type: "fake_type",
|
|
||||||
Component: configs.Initialize,
|
|
||||||
// don't fail on timestamp diff
|
|
||||||
Timestamp: response.Timestamp,
|
|
||||||
Error: "Requested type: fake_type, not found",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, expected, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnknownComponent(t *testing.T) {
|
|
||||||
response, err := getResponse(unknownComponent)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := configs.WsMessage{
|
|
||||||
SessionID: response.SessionID,
|
|
||||||
Type: configs.UI,
|
|
||||||
Component: "fake_component",
|
|
||||||
// don't fail on timestamp diff
|
|
||||||
Timestamp: response.Timestamp,
|
|
||||||
Error: "Requested component: fake_component, not found",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, expected, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getResponse(message string) (configs.WsMessage, error) {
|
|
||||||
err := client.WriteJSON(json.RawMessage(message))
|
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return configs.WsMessage{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response configs.WsMessage
|
|
||||||
err = client.ReadJSON(&response)
|
|
||||||
if err != nil {
|
|
||||||
return configs.WsMessage{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user