diff --git a/.hadolint.yml b/.hadolint.yml new file mode 100644 index 0000000..f9f7dc2 --- /dev/null +++ b/.hadolint.yml @@ -0,0 +1,8 @@ +ignored: + # "Multiple consecutive `RUN` instructions. Consider consolidation." + # We've learned to love the layer cache. Considered and disregarded. + - DL3059 + +trustedRegistries: + - docker.io + - ghcr.io diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e2195b9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Go running inside Lambda container", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 42424, + "host": "localhost", + "showLog": true + } + ] +} diff --git a/Makefile b/Makefile index 3d32eac..0f54ef8 100644 --- a/Makefile +++ b/Makefile @@ -184,7 +184,7 @@ build: tidy ## build-lambda: [build]* Builds the Lambda function with current ARCH for local development. build-lambda: tidy @ $(HEADER) "=====> Building Lambda function..." - CGO_ENABLED=0 GOOS=linux $(GO) build -a -trimpath -ldflags="-s -w" -tags lambda.norpc -o localdev/var-runtime/bootstrap . + CGO_ENABLED=0 GOOS=linux $(GO) build -gcflags="all=-N -l" -tags lambda.norpc -o localdev/var-runtime/bootstrap . .PHONY: build-lambda-prod ## build-lambda-prod: [build]* Builds the Lambda function for deployment. diff --git a/cmd/http.go b/cmd/http.go index 238fb74..f910818 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -96,9 +96,9 @@ var httpCmd = &cobra.Command{ } t := NewTable("HTTP Version", "Supported") - t.Row("1.1", displayBool(result.HTTP11)) - t.Row("2", displayBool(result.HTTP2)) - t.Row("3", displayBool(result.HTTP3)) + t.Row("1.1", displayBool(result.HTTP11, fEmoji)) + t.Row("2", displayBool(result.HTTP2, fEmoji)) + t.Row("3", displayBool(result.HTTP3, fEmoji)) fmt.Println(t.Render()) }, diff --git a/cmd/root.go b/cmd/root.go index a1c7864..394a069 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,6 +31,7 @@ var ( fJSON bool fQuiet bool + fEmoji bool fVerbose int fTimeout int @@ -63,7 +64,18 @@ var ( func init() { rootCmd.PersistentFlags().BoolVarP( - &fJSON, "json", "j", false, "Output as JSON.", + &fEmoji, + "emoji", + "E", + parseFlagAsBool("DST_OUTPUT_EMOJI"), + "(DST_OUTPUT_EMOJI) Use emoji in tabular output for boolean values.", + ) + rootCmd.PersistentFlags().BoolVarP( + &fJSON, + "json", + "j", + parseFlagAsBool("DST_OUTPUT_JSON"), + "(DST_OUTPUT_JSON) Output as JSON.", ) rootCmd.PersistentFlags().BoolVarP( &fQuiet, "quiet", "q", false, "Disable all logging output.", diff --git a/cmd/tls.go b/cmd/tls.go index c655f71..8985e90 100644 --- a/cmd/tls.go +++ b/cmd/tls.go @@ -86,7 +86,7 @@ var tlsCmd = &cobra.Command{ os.Exit(0) } - t := NewTable("TLS Version", "Cipher Suites", "Strength") + t := NewTable("TLS Version", "Cipher Suites", "Strength", "PFS", "AEAD") for i := range result.TLSConnections { tlsConnection := result.TLSConnections[i] @@ -94,17 +94,35 @@ var tlsCmd = &cobra.Command{ for j := range tlsConnection.CipherSuites { cipher := tlsConnection.CipherSuites[j] - if tlsConnection.Version == "TLS v1.3" { - cipher.IANAName = "(Standardized 1.3 suites)" - } + // if tlsConnection.VersionID == httptls.VersionTLS13 { + // cipher.IANAName = "(Standardized 1.3 suites)" + // } if j == 0 && i == 0 { - t.Row(tlsConnection.Version, cipher.IANAName, cipher.Strength) + t.Row( + tlsConnection.Version, + cipher.IANAName, + cipher.Strength, + displayBool(cipher.IsPFS, fEmoji), + displayBool(cipher.IsAEAD, fEmoji), + ) } else if j == 0 { t.Row("", "", "") - t.Row(tlsConnection.Version, cipher.IANAName, cipher.Strength) + t.Row( + tlsConnection.Version, + cipher.IANAName, + cipher.Strength, + displayBool(cipher.IsPFS, fEmoji), + displayBool(cipher.IsAEAD, fEmoji), + ) } else { - t.Row("", cipher.IANAName, cipher.Strength) + t.Row( + "", + cipher.IANAName, + cipher.Strength, + displayBool(cipher.IsPFS, fEmoji), + displayBool(cipher.IsAEAD, fEmoji), + ) } } } diff --git a/cmd/utils.go b/cmd/utils.go index f6adac9..cf82933 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -110,10 +110,26 @@ func NewTable(headers ...string) *table.Table { Headers(headers...) } -func displayBool(b bool) string { +func displayBool(b, useEmoji bool) string { + yes := "YES" + no := "NO" + + if useEmoji { + yes = "✅" + no = "❌" + } + if b { - return "YES" + return yes + } + + return no +} + +func parseFlagAsBool(env string) bool { + if os.Getenv(env) == "true" { + return true } - return "NO" + return false } diff --git a/docs/localdev.md b/docs/localdev.md index a7d5787..73c9a8d 100644 --- a/docs/localdev.md +++ b/docs/localdev.md @@ -2,34 +2,62 @@ ## Prerequisites -* [Docker Desktop](https://docker.com/desktop) +* A *nix environment (e.g., Linux, macOS) +* [Docker Desktop] + * [Recommended settings](https://github.com/northwood-labs/macos-for-development/wiki/Docker-Desktop#recommended-settings) +* [Bash] 5.x shell + * [Recommended settings](https://github.com/skyzyx/bash-mac/blob/master/RECOMMENDED_SETTINGS.md) +* [Go] +* [Hugo] +* [Homebrew] (macOS) + * `export HOMEBREW_CASK_OPTS="--no-quarantine"` * An HTTP client (Recommendations:) * [RapidAPI](https://paw.cloud) (formerly _Paw_) * [Insomnia](https://insomnia.rest) +### Platform notes + +* **macOS** — Set up your environment with [Homebrew] as documented, which will include the [Xcode CLI Tools]. +* **Linux** — Install your platform's standard developer tools. This is different for different families of Linux distributions. +* **Windows** — Run Linux via [Windows Subsystem for Linux v2][WSL2] (WSL2). + +## Exposed ports + +When running as a Lambda function for local development, Docker exposes certain ports on your host machine. + +| Port | Description | +|---------|-----------------------------------------------------------------------------------------------------------------------------| +| `1313` | Localhost endpoint for the website ([Hugo]). Returns HTML. | +| `6379` | [Valkey] cache server. Redis 7.2-compatible. | +| `8080` | Localhost endpoint for the local Lambda service that works the way real Lambda will work. (Compatible with documented API.) | +| `9000` | Direct local Lambda function interface (low-level [RIE] interface for protocol debugging). | +| `42424` | [Delve] debugging protocol for Go. | + ## Start backend services -First, login to `ghcr.io`. +1. [Generate a new _Personal Access Token_](https://github.com/settings/tokens/new?description=DevSecTools%20localdev&scopes=read:packages&default_expires_at=90), with `read:packages` scope. Save it to your password manager. -```bash -echo -n "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin -``` +1. Then, login to `ghcr.io`. This token is represented by `GHCR_TOKEN`. Your GitHub username is represented by `GHCR_USER`. -The local versions of backend services run as containers. From the root of the repository: + ```bash + echo -n "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin + ``` -```bash -make build-lambda -cd localdev -docker compose up -``` +1. The local versions of backend services run as containers. From the root of the repository: -The very first time you run `docker compose up`, the Docker images will need to build. Subsequent runs will leverage the cached completed image. + ```bash + make build-lambda + cd localdev + docker compose up + ``` -When you are done, terminate the containers. + The very first time you run `docker compose up`, the Docker images will need to build. Subsequent runs will leverage the cached completed image. Any time the `Dockerfile` or `docker-compose.yml` are changed, it is a good idea to explicitly run `docker compose up --build`. -```bash -docker compose down -``` +1. When you are done, terminate the containers. + + ```bash + docker compose down + ``` Operating Docker Desktop and Docker Compose is outside the scope of these instructions, but you can read the documentation for yourself. @@ -88,11 +116,11 @@ For local testing, the CLI exposes a very simple HTTP/1.1 server at 0 { var versionStr string + + versionInt := int(version) + switch version { case VersionSSL20: versionStr = TLSVersion[VersionSSL20] @@ -429,6 +432,7 @@ func GetSupportedTLSVersions(domain, port string, opts ...Options) (TLSResult, e versionStr = TLSVersion[VersionTLS13] } results <- TLSConnection{ + VersionID: versionInt, Version: versionStr, CipherSuites: suites, } diff --git a/pkg/httptls/key_exchange.go b/pkg/httptls/key_exchange.go index c6473db..12573ad 100644 --- a/pkg/httptls/key_exchange.go +++ b/pkg/httptls/key_exchange.go @@ -49,3 +49,12 @@ var KeyExchangeList = map[KeyExchange]string{ KexSRP: "Secure Remote Password (SRP)", KexSM2: "ShangMi-2 (SM2)", } + +// PFSList is a map of key exchange algorithms which fall under the definition +// of Perfect Forward Secrecy (PFS). Perfect Forward Secrecy (PFS) is a property +// of secure communication protocols in which compromise of long-term keys does +// not compromise past session keys. +var PFSList = map[KeyExchange]bool{ + KexDHE: true, + KexECDHE: true, +} diff --git a/pkg/httptls/structs.go b/pkg/httptls/structs.go index ab8972d..3af6e5b 100644 --- a/pkg/httptls/structs.go +++ b/pkg/httptls/structs.go @@ -62,6 +62,9 @@ type ( // TLSConnection represents a single TLS connection, and is part of the TLSResult struct. TLSConnection struct { + // VersionID represents the version of TLS as an integer. + VersionID int `json:"versionId,omitempty"` + // Version represents the version of TLS. Version string `json:"version,omitempty"` @@ -81,9 +84,14 @@ func (c *CipherData) Populate() { c.Hash = HashList[c.hash] // Apply PFS settings - if c.keyExchange == KexDHE || c.keyExchange == KexECDHE { + if _, ok := PFSList[c.keyExchange]; ok { c.IsPFS = true } + + // Apply AEAD settings + if _, ok := AEADList[c.encryptionAlgo]; ok { + c.IsAEAD = true + } } func handleOpts(opts []Options) *Options {