Skip to content
Snippets Groups Projects
Commit 7eb106ce authored by Nicolas Pernoud's avatar Nicolas Pernoud
Browse files

feat: minimal base for server and testing

parent fcf32d80
No related branches found
No related tags found
No related merge requests found
Showing
with 1306 additions and 0 deletions
{
// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
// Pointez pour afficher la description des attributs existants.
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug GLC Pro",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceFolder}/main.go",
"env": {
"HTTPS_PORT": "1443"
},
"showLog": true
}
]
}
# Dockerfile from https://github.com/chemidy/smallest-secured-golang-docker-image
##################################
# STEP 1 build executable binary #
##################################
FROM golang:alpine as builder
# Install git + SSL ca certificates.
# Git is required for fetching the dependencies.
# Ca-certificates is required to call HTTPS endpoints.
RUN apk update && apk add --no-cache git ca-certificates tzdata libcap mailcap && update-ca-certificates
# Create appuser
ENV USER=appuser
ENV UID=1000
# See https://stackoverflow.com/a/55757473/12429735
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
WORKDIR /app
ADD . .
RUN chown -Rf "${UID}" ./*
# Get dependencies and run tests
RUN go version
RUN go get -d -v && \
CGO_ENABLED=0 go test ./...
# Build the binary
RUN CGO_ENABLED=0 go build \
-ldflags='-w -s -extldflags "-static"' -a \
-o /app/vestibule .
# Allow running on ports < 1000
RUN setcap cap_net_bind_service=+ep /app/vestibule
##############################
# STEP 2 build a small image #
##############################
FROM scratch
WORKDIR /app
# Import global resources from builder
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /etc/mime.types /etc/mime.types
# Copy static executable and application resources
COPY --from=builder /app/vestibule /app/vestibule
COPY --from=builder /app/dev_certificates /app/dev_certificates
COPY --from=builder /app/web /app/web
COPY --from=builder /app/configs /app/configs
# Use an unprivileged user.
USER appuser:appuser
# Run the binary
ENTRYPOINT ["./vestibule"]
\ No newline at end of file
LICENSE 0 → 100644
This diff is collapsed.
File added
-----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIUfo2bvS5ezMsmIg/+tcoi7J9f+kYwDQYJKoZIhvcNAQEL
BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
DTIwMDUyNTE1MTQwMVoXDTIzMDMxNTE1MTQwMVowKTELMAkGA1UEBhMCVVMxGjAY
BgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxEdB08lXq6kKrBYG14akHfTE/ybJwvj6Ap07J08rmDMwDf4bbhI7
9aEBKtf3QvdQgYqSp7iacc5+OG30FxBnWavShcWUrpTXIqIm18lMkq5cMt1GGRVL
9cwX4P8ioBfYvLZgkesC/xzH2691J0uxWIVVwqmmJ2LxCTVZD3lT4j5ZqEwr0eDA
8zuDqcaQx/NugMmtUinBkDkbsohTeri9dq39dy6w3uqVtxuyxpeS+rTobYWCQMIl
7K1Brt6oZQrqKOhVVeYYPeZpqxRz3YYnv+z7wcEqY+RmXMVGJxYhU198HP5Pkmbe
xo5XGZ+eMxEA3fGDJA4l9s1I+LV60Ol0VwIDAQABo1MwUTAdBgNVHQ4EFgQUZ0a/
g3bbnsuKcB83xig/ZVq9v7cwHwYDVR0jBBgwFoAUZ0a/g3bbnsuKcB83xig/ZVq9
v7cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAwZT6dWF+iE+X
0YUoqiDsWAf/QCgBRROS8VI1z8N3tdtHzx1cHRpGjhOkYnm3AE4GVpG0V32W54g2
098rKqpFUK3aYIDQXDzJ706FeM6VQCxTlGoTiT6Sfv5DBFcAp6V4CFd4XDgH9A2b
y/iPBPhquxMwMOLUfpWAAg30QrAbgWh61avRClcSRxiX4Pf/ZVx4Sx0rdptRSRbP
xYbi4sktODDwyaV5BC8srBN+OrEPALSu3AVRYW1BS5K8fI051p21JlecCYkRm7Xf
1xQyUxq87r9w7FEyR8QotnuYcnqzpYpLw5k0xfNcXF3J49fWennAnFTI2SHF7xyy
QvUti4q56w==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDER0HTyVerqQqs
FgbXhqQd9MT/JsnC+PoCnTsnTyuYMzAN/htuEjv1oQEq1/dC91CBipKnuJpxzn44
bfQXEGdZq9KFxZSulNcioibXyUySrlwy3UYZFUv1zBfg/yKgF9i8tmCR6wL/HMfb
r3UnS7FYhVXCqaYnYvEJNVkPeVPiPlmoTCvR4MDzO4OpxpDH826Aya1SKcGQORuy
iFN6uL12rf13LrDe6pW3G7LGl5L6tOhthYJAwiXsrUGu3qhlCuoo6FVV5hg95mmr
FHPdhie/7PvBwSpj5GZcxUYnFiFTX3wc/k+SZt7GjlcZn54zEQDd8YMkDiX2zUj4
tXrQ6XRXAgMBAAECggEATHFEHeuojV6EnYjmmUIC4PfXUVgV9C1rkWETDLg0zk4B
RTHWFQRc/cj632jFD/uqCwmhHFk1LWWymt+X1PO6jJ3ym87bnTKypvZoElp/zBDE
L7sl9dbpgo0+L46uOZOQAc81xR62SA05eUHYarl8FHG5eNsBrw1FusRgLzqdhNr8
AOZRhSC3o6Fy74BGSpeF+i81RfvDv2eOsMXV8gG2jMfS4b+qJaduagKnGvYUXYPU
mYu/C3eH1BZ8ru/r6/lIpmwquFa+ol7zT2TBhJnAMPaWCGhMTGY182HgC8kAM9zd
X+SukQM6jQ9w+SB1+LdMX8PtUqnsbBxyjk+o3VAhgQKBgQD6HRUI9Vrg1p+q6qhh
2lyiNztMNwEMEslt9tw89v6NA/kdJwiZo9LpLEpC7loJU0UOoYOx9fyJY/A8XuzA
LpJGbu6DsXmbXry9meXUtpDpg1p3/TvndPYEMczN5TC0ulfqFILJl9xE6VDY+iiO
dHbKbkMcj6Kj/vKRKKwZbkH7JwKBgQDI5dI2XWc3fNf1n26GHkaDkwrf1JRAOuz9
Xb+EuStcEpj06yS6TUAnStFD5ei7kPffmbG64FmodA3F1F8+OqvyFP922QFXBznS
wi/bMmY155zBUgmRMt4m2xhew98gKYcjBBbCdhcaPhFXXLZrKgdTXxvqOYSC1JS5
OrDtEgE7UQKBgCnVx3jyITtOavKZwYRsk+YGOwLqiGBOnCaqjfgckENiEL8Rklyw
jEy4PcuoNqMsrHZgCbkwngcVEk/myM70ez8QuxCIFQEp/YPxRAIqb16/u9gqIWT7
qVLWSZ1+4oC4UpAy0hWKMyvLotxIK5R4Yo5FlzAlzlyD/mrNVMwEJ0EtAoGAcx8X
deXSRv64dl01JY/l0nIJkjgr+B6MPahiHjlJ+ooBsp+ovUWVVK7P+jueQRWSpURa
j38OgbR3S/0l0vAmJiQYPE+gL/HJFdgHOnHIFm2x6yoKp3ThzpxQe8V8b968PLmU
HT00geo9X7WWJxS/e+wNgAwTvjD5RKe4STG/GPECgYBARtCcodComDRSkWQ7nGx+
LAW/dvKoJwAb+jRNyl/1ug5B6s+hyM6JP7A73IDfkBi18xleH85iZXzXtOa0MbP+
v83wF6kEYEhO4f9b3653k0qZo1S0BkgJcs4R4lkea1JifpTZBdM+GuKaX/+NkUSC
ySFackZ781xgX4TjFTGLMg==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIUfo2bvS5ezMsmIg/+tcoi7J9f+kYwDQYJKoZIhvcNAQEL
BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
DTIwMDUyNTE1MTQwMVoXDTIzMDMxNTE1MTQwMVowKTELMAkGA1UEBhMCVVMxGjAY
BgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxEdB08lXq6kKrBYG14akHfTE/ybJwvj6Ap07J08rmDMwDf4bbhI7
9aEBKtf3QvdQgYqSp7iacc5+OG30FxBnWavShcWUrpTXIqIm18lMkq5cMt1GGRVL
9cwX4P8ioBfYvLZgkesC/xzH2691J0uxWIVVwqmmJ2LxCTVZD3lT4j5ZqEwr0eDA
8zuDqcaQx/NugMmtUinBkDkbsohTeri9dq39dy6w3uqVtxuyxpeS+rTobYWCQMIl
7K1Brt6oZQrqKOhVVeYYPeZpqxRz3YYnv+z7wcEqY+RmXMVGJxYhU198HP5Pkmbe
xo5XGZ+eMxEA3fGDJA4l9s1I+LV60Ol0VwIDAQABo1MwUTAdBgNVHQ4EFgQUZ0a/
g3bbnsuKcB83xig/ZVq9v7cwHwYDVR0jBBgwFoAUZ0a/g3bbnsuKcB83xig/ZVq9
v7cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAwZT6dWF+iE+X
0YUoqiDsWAf/QCgBRROS8VI1z8N3tdtHzx1cHRpGjhOkYnm3AE4GVpG0V32W54g2
098rKqpFUK3aYIDQXDzJ706FeM6VQCxTlGoTiT6Sfv5DBFcAp6V4CFd4XDgH9A2b
y/iPBPhquxMwMOLUfpWAAg30QrAbgWh61avRClcSRxiX4Pf/ZVx4Sx0rdptRSRbP
xYbi4sktODDwyaV5BC8srBN+OrEPALSu3AVRYW1BS5K8fI051p21JlecCYkRm7Xf
1xQyUxq87r9w7FEyR8QotnuYcnqzpYpLw5k0xfNcXF3J49fWennAnFTI2SHF7xyy
QvUti4q56w==
-----END CERTIFICATE-----
5F682FCE938D26EBE33AC179E7E43EF06E8701C7
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.127.0.0.1.nip.io
DNS.3 = glcpro.127.0.0.1.nip.io
DNS.4 = *.glcpro.127.0.0.1.nip.io
#!/bin/bash
rm -f *.crt *.csr *.key *.pem *.srl
openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=Vestibule-Root-CA"
openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Vestibule-Certificates/CN=localhost.local"
openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.ext -out localhost.crt
-----BEGIN CERTIFICATE-----
MIIDxzCCAq+gAwIBAgIUX2gvzpONJuvjOsF55+Q+8G6HAccwDQYJKoZIhvcNAQEL
BQAwKTELMAkGA1UEBhMCVVMxGjAYBgNVBAMMEVZlc3RpYnVsZS1Sb290LUNBMB4X
DTIwMDUyNTE1MzU1M1oXDTIzMDMxNTE1MzU1M1owbzELMAkGA1UEBhMCVVMxEjAQ
BgNVBAgMCVlvdXJTdGF0ZTERMA8GA1UEBwwIWW91ckNpdHkxHzAdBgNVBAoMFlZl
c3RpYnVsZS1DZXJ0aWZpY2F0ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlreNyd0Vukg3is7TKZc6VY
ElnOYjeN4/AwiaXyPrO0FbZ5U0hPPQvSzBlAfraU3f62sQ4i6sgGNOP5PbPoyC4e
WOzpUfnwZartpbyfNZoMSLYpUUN26wErfn1XC6UjTZtTNT5ooTulTwyQ6LBY1syL
amDPRagCwDqPw9lwwLlaRe3IjXmSLyoFhkm1rtYkQIwe6jdEaoWkDnQCTHL+zIUr
GXyR4Wib3Y02dfmTJEQ+7QgXTEqDf8rsTqLLjltL3XzE3VqjmhN5KdmeRaFcogSZ
nxtYT0bAQVFh/S+de11atQ+oCZMs/yIEysepIs+QlYdIsdF8t/F53EvawKYsfiMC
AwEAAaOBoDCBnTAfBgNVHSMEGDAWgBRnRr+Ddtuey4pwHzfGKD9lWr2/tzAJBgNV
HRMEAjAAMAsGA1UdDwQEAwIE8DBiBgNVHREEWzBZgglsb2NhbGhvc3SCEiouMTI3
LjAuMC4xLm5pcC5pb4IadmVzdGlidWxlLjEyNy4wLjAuMS5uaXAuaW+CHCoudmVz
dGlidWxlLjEyNy4wLjAuMS5uaXAuaW8wDQYJKoZIhvcNAQELBQADggEBAD5hvstw
d/3obDooaoG1wLX9w025/u+hiwiIFNTNPf6MLSD1SuXJsugZWG248cDWtO6OfydD
VsgtAK2n3eELXZr7R3eCFGBvaRumDcr8mkYlDOym84blwAnoU10E376IBadr48S2
bQsTwjMvq93sUdujWhEB4R9mErjJ/khklH5Qk+OGUPv+V/TBU8kLsSqhqI0J3bps
iMxddZleLMzkaLl0DOfFU46IhRKrjxAKgBHTdW4darh3Dd7RqmOmO4t35V/ht79L
1gE1jqNprgC4Ej2TtqrcGwszEpCGye5E0w6+jNANfQ3j06cU6gXVZ0idpuh/ywtD
4MgoKu7KHp9OmTo=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE REQUEST-----
MIICtDCCAZwCAQAwbzELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCVlvdXJTdGF0ZTER
MA8GA1UEBwwIWW91ckNpdHkxHzAdBgNVBAoMFlZlc3RpYnVsZS1DZXJ0aWZpY2F0
ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANlreNyd0Vukg3is7TKZc6VYElnOYjeN4/AwiaXyPrO0FbZ5
U0hPPQvSzBlAfraU3f62sQ4i6sgGNOP5PbPoyC4eWOzpUfnwZartpbyfNZoMSLYp
UUN26wErfn1XC6UjTZtTNT5ooTulTwyQ6LBY1syLamDPRagCwDqPw9lwwLlaRe3I
jXmSLyoFhkm1rtYkQIwe6jdEaoWkDnQCTHL+zIUrGXyR4Wib3Y02dfmTJEQ+7QgX
TEqDf8rsTqLLjltL3XzE3VqjmhN5KdmeRaFcogSZnxtYT0bAQVFh/S+de11atQ+o
CZMs/yIEysepIs+QlYdIsdF8t/F53EvawKYsfiMCAwEAAaAAMA0GCSqGSIb3DQEB
CwUAA4IBAQA9fiS4fx3HIDpwWcfYNp2G8iII5aXbbSc8Yf1P6P7WmHx1Hw1+cS7D
cjUZRhX/z19JCkq95ZnC7Fd8GNtFKMrceza0mAPXi4stfX1o2om+tJijHMUiA8GY
NX69vvpe3vVYfKnQJcva8p8y2th2Ye1b96HQKJTbVroxpE9fz5LMJzs7YYFKxxk0
qw1jRerIUbeQL5uip28OGx79TF6M2ZfXKJXqxjWjB5KEzNbutolfdlFtWMwd3Pw0
Ibg3n/vUydPM65JZJQZlYaAP8K/NKBA4YJ8t7GmNGWELq1HVn37yCE2h7HVshI9L
hoaH1Jp4ddLS9m3MCEcwcOvrX7Oy7ByK
-----END CERTIFICATE REQUEST-----
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDZa3jcndFbpIN4
rO0ymXOlWBJZzmI3jePwMIml8j6ztBW2eVNITz0L0swZQH62lN3+trEOIurIBjTj
+T2z6MguHljs6VH58GWq7aW8nzWaDEi2KVFDdusBK359VwulI02bUzU+aKE7pU8M
kOiwWNbMi2pgz0WoAsA6j8PZcMC5WkXtyI15ki8qBYZJta7WJECMHuo3RGqFpA50
Akxy/syFKxl8keFom92NNnX5kyREPu0IF0xKg3/K7E6iy45bS918xN1ao5oTeSnZ
nkWhXKIEmZ8bWE9GwEFRYf0vnXtdWrUPqAmTLP8iBMrHqSLPkJWHSLHRfLfxedxL
2sCmLH4jAgMBAAECggEAPrbZbgm30g9qPOzknqvUx+TXpj/55Lxw+1+E1PFDVOKl
QLVOaJUyrq5nYQFRi6j1P/vCpqk7v+bKatgCDFshIuxuCihjFQ1axis2m2nxNDZG
4wSYBDXRI80/9zecjE3anOsreI0FtQbB/oXuGLAHhYjRbotbNFNWz42YmKgFb3Eq
hwuyvuZ6BRYLv5bFYWTRgIuK62RiM6Y0s0RC9dmn2y6k5s3A2E+hTEKGiYZVseYH
Fj0OtFDj5FqTqgd0j6orunkkgzkUe0TrYNvvkC172quU9yj1h4wRZ0bdc+wQbj/k
MMWcpXWPHY/jAGyUN17iOpP4mldxAhb2lP12Ee5xQQKBgQDxRyVy6OwTMxti1ozN
swUIf/X+UVappfh9BVfHkQN1CR/W7H5nR57Nwuig5oHxf+eBu6qGOyXypT3eSUvy
Mp1U3EiPeDSqGGZBAkpuKcv4Bo6uIVyx5NvmTdKg+QujqHbadbJJULSvZGmtFLje
yK3exTzC6M+qizcdiAylpsF5NQKBgQDmr6cyQXAlH/kvnZf3PFWsVCKzCSXL0LbU
lMmiwU9VeA5K1LnHZjWXe9D9iaFMAZwXY1YQeAY6ob9yj/MdveD5BIXaA2Z9vBx2
JHj+2JEK86Zo3kbYa0uyLgZAAgF6nSjwlbXeArzvYwzA9VrObYwF0vi26+zs/Gp+
5qKDOdvc9wKBgQCIu50JZd6Dcu8OMDBTUiSEEKBDo6zNL3WGxEReoFupM6aLkjKA
LSJd0RssM/Vn+ugjqUqWD2cN/370Pe2AMBbhRIUKaUDPNEyegsHTdHRW9sBdBiMV
3XCRL4EyIHDsKkhQvWpwCgCbdPg+UgQCnQ+Ls5Mfkfg92e8+GR+xsLLsEQKBgQCz
05nygzB4wEPv6lncURw4Ch5Cko70Iu9jJ9lHFopRhhUeIFXyKqr8O3jfK7qhFWAo
mzcr/2RbsFsdYzmRpcMKW4w8NwpeWZWWQzhO8WOfte+zMBLqAjov29DjdKzSeFDB
uvLa7id6NSxr/teas6rDn4JiledLljkuU3u78A6EYQKBgQCAh4wxoOsz2kRKXCBV
y1SBCG/7hBCCRDpXtBgwgqa5Z514XpkGgU5GuQ0OgzbO15btj4H7SI9hrytujIfX
y0dFCmBRIUA2A8n4eVP46OslVwGffOhESYvLwZb6ntYAApyM+WwlLl8YYxil/hqb
mBiXwYJP9zJ/52aWhBs4NZOcHA==
-----END PRIVATE KEY-----
go.mod 0 → 100644
module forge.grandlyon.com/npernoud/glcpro
go 1.16
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/oschwald/maxminddb-golang v1.8.0
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 // indirect
)
go.sum 0 → 100644
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package rootmux
import (
"fmt"
"net/http"
"forge.grandlyon.com/npernoud/glcpro/pkg/common"
"forge.grandlyon.com/npernoud/glcpro/pkg/middlewares"
)
// CreateRootMux creates a RootMux
func CreateRootMux(staticDir string) *http.ServeMux {
// Create the main handler
mainMux := http.NewServeMux()
apiMux := http.NewServeMux()
apiMux.HandleFunc("/healthcheck", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, "OK")
})
mainMux.Handle("/api/", http.StripPrefix("/api", apiMux))
// Serve static files falling back to serving index.html
mainMux.Handle("/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
// Put it together into the main handler
return mainMux
}
package rootmux
import (
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"sync"
"testing"
"forge.grandlyon.com/npernoud/glcpro/pkg/tester"
)
var (
noH map[string]string
)
func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
// Create the server
mux := CreateRootMux("web")
ts := httptest.NewServer(mux)
url, _ := url.Parse(ts.URL)
port := url.Port()
// Create the cookie jar
jar, _ := cookiejar.New(nil)
// wrap the testing function
return ts, tester.CreateServerTester(t, port, jar), tester.CreateServerTester(t, port, nil)
}
func TestAll(t *testing.T) {
// Create the tester
// ATTENTION : The tester (do function) Cookie Jar is shared between test groups.
// So it's like each test group is executed at the same time by the same user, with the same browser.
// If needed, create a DoFn in each group.
ts, do, _ := createTester(t)
defer ts.Close()
testGroup1 := createTestGroup1(do)
testGroup2 := createTestGroup2(do)
// RUN THE TESTS CONCURRENTLY
var wg sync.WaitGroup
functions := []func(wg *sync.WaitGroup){testGroup1, testGroup2}
for _, f := range functions {
wg.Add(1)
go f(&wg)
}
wg.Wait()
}
func createTestGroup1(do tester.DoFn) func(wg *sync.WaitGroup) {
// Create the tester
return func(wg *sync.WaitGroup) {
defer wg.Done()
// Try the healtcheck (must pass)
do("GET", "/api/healthcheck", noH, "", http.StatusOK, "OK")
}
}
func createTestGroup2(do tester.DoFn) func(wg *sync.WaitGroup) {
// Create the tester
return func(wg *sync.WaitGroup) {
defer wg.Done()
// Try the healtcheck (must pass)
do("GET", "/api/healthcheck", noH, "", http.StatusOK, "OK")
}
}
main.go 0 → 100644
package main
import (
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"forge.grandlyon.com/npernoud/glcpro/pkg/common"
"forge.grandlyon.com/npernoud/glcpro/internal/rootmux"
"forge.grandlyon.com/npernoud/glcpro/pkg/log"
)
var (
logFile = common.StringValueFromEnv("LOG_FILE", "") // Optional file to log to
httpsPort = common.IntValueFromEnv("HTTPS_PORT", 443) // HTTPS port to serve on
debugMode = common.BoolValueFromEnv("DEBUG_MODE", false) // Debug mode, disable Let's Encrypt, enable CORS and more logging
)
func main() {
// Initialize logger
if logFile != "" {
log.SetFile(logFile)
// Properly close the log on exit
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
log.Logger.Println("--- Closing log ---")
log.CloseFile()
os.Exit(0)
}()
}
log.Logger.Println("--- Server is starting ---")
log.Logger.Printf("--- Server is listening on https://localhost:%v ---\n", strconv.Itoa(httpsPort))
// Create the server
rootMux := rootmux.CreateRootMux("web")
// Serve locally with https on debug mode or with let's encrypt on production mode
if debugMode {
log.Logger.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", log.Middleware(rootMux)))
} else {
log.Logger.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux))
}
}
package common
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"os"
"path"
"strconv"
"sync"
"forge.grandlyon.com/npernoud/glcpro/pkg/log"
)
var (
disableLogFatal = false
lock sync.Mutex // Mutex used to lock file writing
)
// Save saves a representation of v to the file at path.
func Save(path string, v interface{}) error {
lock.Lock()
defer lock.Unlock()
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
r, err := Marshal(v)
if err != nil {
return err
}
_, err = io.Copy(f, r)
return err
}
// Load loads the file at path into v. Use os.IsNotExist() to see if the returned error is due to the file being missing.
func Load(path string, v interface{}) error {
lock.Lock()
defer lock.Unlock()
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
return Unmarshal(f, v)
}
// Marshal is a function that marshals the object into an io.Reader. By default, it uses the JSON marshaller.
var Marshal = func(v interface{}) (io.Reader, error) {
b, err := json.MarshalIndent(v, "", "\t")
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
// Unmarshal is a function that unmarshals the data from the reader into the specified value. By default, it uses the JSON unmarshaller.
var Unmarshal = func(r io.Reader, v interface{}) error {
return json.NewDecoder(r).Decode(v)
}
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomString(s int) (string, error) {
b, err := GenerateRandomBytes(s)
return base64.URLEncoding.EncodeToString(b), err
}
// FallBackWrapper serves a file if found and else default to index.html
type FallBackWrapper struct {
Assets http.FileSystem
}
// Open serves a file if found and else default to index.html
func (i *FallBackWrapper) Open(name string) (http.File, error) {
file, err := i.Assets.Open(name)
// If the file is found but there is another error or the asked for file has an extension : return the file or error
if !os.IsNotExist(err) || path.Ext(name) != "" {
return file, err
}
// Else fall back to index.html
return i.Assets.Open("index.html")
}
// Contains works out if a string slice contains a given string element
func Contains(a []string, x string) bool {
for _, n := range a {
if x == n {
return true
}
}
return false
}
// StringValueFromEnv set a value into an *interface from an environment variable or default
func StringValueFromEnv(ev string, def string) string {
val := os.Getenv(ev)
if val == "" {
return def
}
return val
}
// IntValueFromEnv set a value into an *interface from an environment variable or default
func IntValueFromEnv(ev string, def int) int {
val := os.Getenv(ev)
if val == "" {
return def
}
v, err := strconv.Atoi(val)
if err != nil && !disableLogFatal {
log.Logger.Fatalf("Error : could not get integer value from environment variable %v=%v\n", ev, val)
}
return v
}
// BoolValueFromEnv set a value into an *interface from an environment variable or default
func BoolValueFromEnv(ev string, def bool) bool {
val := os.Getenv(ev)
if val == "" {
return def
}
v, err := strconv.ParseBool(val)
if err != nil && !disableLogFatal {
log.Logger.Fatalf("Error : could not get boolean value from environment variable %v=%v\n", ev, val)
}
return v
}
package common
import (
"os"
"testing"
)
func init() {
disableLogFatal = true
}
func TestStringValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv string
type args struct {
ev string
def string
}
tests := []struct {
name string
args args
expected string
}{
{"string_value_from_env", args{"MY_EV", "test"}, "from_env"},
{"string_value_from_def", args{"MY_DEF", "test"}, "test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rv = StringValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("StringValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
})
}
}
func TestIntValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv int
type args struct {
ev string
def int
}
tests := []struct {
name string
args args
expected int
}{
{"int_value_from_def", args{"MY_DEF", 1}, 1},
{"string_on_int_from_env", args{"MY_EV", 1}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rv = IntValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("IntValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
})
}
}
func TestBoolValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv bool
type args struct {
ev string
def bool
}
tests := []struct {
name string
args args
expected bool
}{
{"bool_value_from_def", args{"MY_DEF", true}, true},
{"string_on_bool_from_def", args{"MY_EV", true}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rv = BoolValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("BoolValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment