Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

Requests: pin Go 1.27 ML-KEM hybrid CurvePreferences (including P-521 fallback) and print the negotiated key exchange.

### Fix

Devenv: prefer httpbin on 127.0.0.1:8081 and proxy nginx upstreams through the allocated httpbin port so `devenv test` keeps working when the preferred port is already taken; fail fast in request integration tests with `set -e`, enable `pipefail` on success-case request leaf pipelines, and assert request exit status separately from expected error text.

### Tests

Certinfo and requests: share CA/leaf certificate generation and custom TLS httptest servers via internal/tlstest.
Expand Down
53 changes: 42 additions & 11 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ in
listen [::]:9443 ssl;
http2 on;
location / {
proxy_pass http://localhost:8080;
proxy_pass http://127.0.0.1:${toString config.processes.httpbin.ports.main.value};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
Expand All @@ -116,7 +116,7 @@ in
listen [::]:9444 ssl proxy_protocol;
http2 on;
location / {
proxy_pass http://localhost:8080;
proxy_pass http://127.0.0.1:${toString config.processes.httpbin.ports.main.value};
proxy_pass_request_headers on;
proxy_set_header Host $host;
proxy_set_header X-Proxy-Protocol enabled;
Expand All @@ -135,7 +135,7 @@ in
listen [::]:9445 ssl;
http2 on;
location / {
proxy_pass http://localhost:8080;
proxy_pass http://127.0.0.1:${toString config.processes.httpbin.ports.main.value};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
Expand All @@ -150,7 +150,7 @@ in
listen [::]:9446 ssl;
http2 on;
location / {
proxy_pass http://localhost:8080;
proxy_pass http://127.0.0.1:${toString config.processes.httpbin.ports.main.value};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
Expand All @@ -165,7 +165,7 @@ in
listen [::]:9447 ssl;
http2 on;
location / {
proxy_pass http://localhost:8080;
proxy_pass http://127.0.0.1:${toString config.processes.httpbin.ports.main.value};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
Expand All @@ -175,6 +175,7 @@ in

services.httpbin = {
enable = true;
bind = [ "127.0.0.1:8081" ];
};

tasks."web:refreshCertsBeforeNginxStart" = {
Expand Down Expand Up @@ -325,6 +326,7 @@ in

scripts.test-requests-show-sample-config.exec = ''
gum format "## test request show sample config"
set -o pipefail
./dist/https-wrench requests --show-sample-config| grep 'requests:'
'';

Expand All @@ -345,18 +347,29 @@ in

scripts.test-requests-timeout.exec = ''
gum format "## test request timeout"
time ./dist/https-wrench requests --config ./${config.env.EXAMPLES}/https-wrench-request-timeout.yaml | grep "Client.Timeout exceeded while awaiting headers"
set +e
out=$(./dist/https-wrench requests --config ./${config.env.EXAMPLES}/https-wrench-request-timeout.yaml 2>&1)
status=$?
set -e
printf '%s\n' "$out" | grep "Client.Timeout exceeded while awaiting headers"
# requests prints per-request errors but exits 0
test "$status" -eq 0
'';

scripts.test-requests-unknown-ca.exec = ''
gum format "## test request with unknown CA"

set +o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/unknown-ca.yaml | grep 'failed to verify certificate: x509: certificate signed by unknown authority'
set +e
out=$(./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/unknown-ca.yaml 2>&1)
status=$?
set -e
printf '%s\n' "$out" | grep 'failed to verify certificate: x509: certificate signed by unknown authority'
# requests prints per-request errors but exits 0
test "$status" -eq 0
'';

scripts.test-requests-insecure.exec = ''
gum format "## test request insecure skip verify"
set -o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/insecure.yaml | grep 'StatusCode: 200'
'';

Expand All @@ -367,31 +380,47 @@ in

scripts.test-requests-body-regexp-match.exec = ''
gum format "## test request body regexp match"
set -o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/body-regexp-match.yaml --ca-bundle $CAROOT/rootCA.pem | grep 'BodyRegexpMatch: true'
'';

scripts.test-requests-ca-bundle-file-success.exec = ''
gum format "## test request with CA bundle file"
set -o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/ca-bundle-200.yaml --ca-bundle $CAROOT/rootCA.pem | grep "StatusCode: 200"
'';

scripts.test-requests-valid-cert-wrong-ca-bundle.exec = ''
gum format "## test request with valid cert and wrong CA bundle file"
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/repo-os76.yaml --ca-bundle $CAROOT/rootCA.pem 2>&1 | grep 'certificate signed by unknown authority'
set +e
out=$(./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/repo-os76.yaml --ca-bundle $CAROOT/rootCA.pem 2>&1)
status=$?
set -e
printf '%s\n' "$out" | grep 'certificate signed by unknown authority'
# requests prints per-request errors but exits 0
test "$status" -eq 0
'';

scripts.test-requests-ca-bundle-file-wrong-servername.exec = ''
gum format "## test request with CA bundle file and wrong host name / servername"
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/ca-bundle-wrong-servername.yaml --ca-bundle $CAROOT/rootCA.pem | grep 'tls: failed to verify certificate: x509'
set +e
out=$(./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/ca-bundle-wrong-servername.yaml --ca-bundle $CAROOT/rootCA.pem 2>&1)
status=$?
set -e
printf '%s\n' "$out" | grep 'tls: failed to verify certificate: x509'
# requests prints per-request errors but exits 0
test "$status" -eq 0
'';

scripts.test-requests-proxy-protocol-ipv4.exec = ''
gum format "## test request proxy protocol IPv4"
set -o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/proxy-protocol-ipv4.yaml --ca-bundle $CAROOT/rootCA.pem | grep '192.0.2.1'
'';

scripts.test-requests-proxy-protocol-ipv6.exec = ''
gum format "## test request proxy protocol IPv6"
set -o pipefail
./dist/https-wrench requests --config ./${config.env.EXAMPLES}/tests-configs/proxy-protocol-ipv6.yaml --ca-bundle $CAROOT/rootCA.pem | grep '2001:db8::1'
'';

Expand All @@ -405,6 +434,7 @@ in
echo "caBundle: |" >> $CA_BUNDLE_YAML_TEST_FILE
while IFS= read -r line; do echo " $line" >> $CA_BUNDLE_YAML_TEST_FILE ; done < $CAROOT/rootCA.pem

set -o pipefail
./dist/https-wrench requests --config $CA_BUNDLE_YAML_TEST_FILE | grep 'StatusCode: 200'
'';

Expand Down Expand Up @@ -623,6 +653,7 @@ in
'';

scripts.run-requests-tests.exec = ''
set -e
Comment thread
coderabbitai[bot] marked this conversation as resolved.
gum format "## Requests tests"

# test-requests-sample-config
Expand Down
6 changes: 1 addition & 5 deletions internal/certinfo/certinfo_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,7 @@ func (c *Config) printCACerts(w io.Writer, ks, sl, sv lipgloss.Style) error {
inputReader,
)
if err != nil {
return fmt.Errorf(
"unable for read Root certificates from %s: %w",
c.CACertsFilePath,
err,
)
return fmt.Errorf("unable to read Root certificates: %w", err)
}

CertsToTables(w, rootCerts)
Expand Down
2 changes: 1 addition & 1 deletion internal/certinfo/certinfo_handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ func TestCertinfo_PrintData(t *testing.T) {

errPrint := cc.PrintData(context.Background(), &buffer)
require.Error(t, errPrint)
require.ErrorContains(t, errPrint, "unable for read Root certificates")
require.ErrorContains(t, errPrint, "unable to read Root certificates")
})
}

Expand Down
4 changes: 2 additions & 2 deletions internal/certinfo/certinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var certinfoConfigFileReadErrorTests = []struct {
expectMsg: map[string]string{
"caPool": "failed to read CA bundle file: unable to read file testdata/unreadable-file.txt",
"certs": "error reading certificate file: unable to read file testdata/unreadable-file.txt",
"key": "unable to read file testdata/unreadable-file.txt",
"key": "error reading private key file: unable to read file testdata/unreadable-file.txt",
},
},
{
Expand All @@ -66,7 +66,7 @@ var certinfoConfigFileReadErrorTests = []struct {
expectMsg: map[string]string{
"caPool": "failed to read CA bundle file: open testdata/not-exist: no such file or directory",
"certs": "error reading certificate file: open testdata/not-exist: no such file or directory",
"key": "open testdata/not-exist: no such file or directory",
"key": "error reading private key file: open testdata/not-exist: no such file or directory",
},
},
{
Expand Down
35 changes: 17 additions & 18 deletions internal/certinfo/common_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -42,7 +41,7 @@ func certMatchPrivateKey(cert *x509.Certificate, key crypto.PrivateKey) (bool, e

pub, ok := cert.PublicKey.(interface{ Equal(crypto.PublicKey) bool })
if !ok {
return false, errors.New("unsupported public key type in certificate")
return false, ErrUnsupportedPublicKey
}

signer, ok := key.(crypto.Signer)
Expand All @@ -56,11 +55,11 @@ func certMatchPrivateKey(cert *x509.Certificate, key crypto.PrivateKey) (bool, e
// GetRootCertsFromFile reads a PEM bundle from a file and returns an x509 CertPool.
func GetRootCertsFromFile(caBundlePath string, fileReader Reader) (*x509.CertPool, error) {
if caBundlePath == emptyString {
return nil, errors.New("empty string provided as caBundlePath")
return nil, &EmptyArgError{Name: "caBundlePath"}
}

if fileReader == nil {
return nil, errors.New("nil Reader provided")
return nil, ErrNilReader
}

certsFromFile, err := fileReader.ReadFile(caBundlePath)
Expand All @@ -70,7 +69,7 @@ func GetRootCertsFromFile(caBundlePath string, fileReader Reader) (*x509.CertPoo

rootCAPool := x509.NewCertPool()
if ok := rootCAPool.AppendCertsFromPEM(certsFromFile); !ok {
return nil, errors.New("unable to create CertPool from file")
return nil, ErrCertPoolFromFile
}

return rootCAPool, nil
Expand All @@ -79,12 +78,12 @@ func GetRootCertsFromFile(caBundlePath string, fileReader Reader) (*x509.CertPoo
// GetRootCertsFromString parses a PEM bundle from a string and returns an x509 CertPool.
func GetRootCertsFromString(caBundleString string) (*x509.CertPool, error) {
if caBundleString == emptyString {
return nil, errors.New("empty string provided as caBundleString")
return nil, &EmptyArgError{Name: "caBundleString"}
}

rootCAPool := x509.NewCertPool()
if ok := rootCAPool.AppendCertsFromPEM([]byte(caBundleString)); !ok {
return nil, errors.New("no valid certs in caBundle config string")
return nil, ErrNoCertsInConfig
}

return rootCAPool, nil
Expand All @@ -93,11 +92,11 @@ func GetRootCertsFromString(caBundleString string) (*x509.CertPool, error) {
// GetCertsFromBundle reads a PEM bundle from a file and returns a slice of x509 Certificates.
func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certificate, error) {
if certBundlePath == emptyString {
return nil, errors.New("empty string provided as certBundlePath")
return nil, &EmptyArgError{Name: "certBundlePath"}
}

if fileReader == nil {
return nil, errors.New("nil Reader provided")
return nil, ErrNilReader
}

certPEM, err := fileReader.ReadFile(certBundlePath)
Expand Down Expand Up @@ -131,7 +130,7 @@ func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certi
}

if len(certs) == 0 {
return nil, fmt.Errorf("no valid certificates found in file %s", certBundlePath)
return nil, &NoCertsInFileError{Path: certBundlePath}
}

return certs, nil
Expand All @@ -142,7 +141,7 @@ func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certi
func IsPrivateKeyEncrypted(key []byte) (bool, error) {
keyBlock, _ := pem.Decode(key)
if keyBlock == nil {
return false, errors.New("failed to decode PEM")
return false, ErrPEMDecode
}

switch keyBlock.Type {
Expand All @@ -152,7 +151,7 @@ func IsPrivateKeyEncrypted(key []byte) (bool, error) {
_, hasDEK := keyBlock.Headers["DEK-Info"] // if encrypted, DEK-Info header exists
return hasDEK, nil
default:
return false, fmt.Errorf("unrecognized private key type: %s", keyBlock.Type)
return false, &UnrecognizedKeyTypeError{Type: keyBlock.Type}
}
}

Expand All @@ -165,7 +164,7 @@ func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string, pwReader Reader) (
}

if pwReader == nil {
return nil, errors.New("nil Reader provided")
return nil, ErrNilReader
}

pkeyEnvPw := os.Getenv(pwEnvKey)
Expand Down Expand Up @@ -213,7 +212,7 @@ func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string, pwReader Reader) (
func ParsePrivateKey(keyPEM []byte, pwEnvKey string, pwReader Reader) (crypto.PrivateKey, error) {
keyBlock, _ := pem.Decode(keyPEM)
if keyBlock == nil {
return nil, errors.New("failed to decode PEM")
return nil, ErrPEMDecode
}

isEncrypted, _ := IsPrivateKeyEncrypted(keyPEM)
Expand Down Expand Up @@ -255,7 +254,7 @@ func ParsePrivateKey(keyPEM []byte, pwEnvKey string, pwReader Reader) (crypto.Pr
return ecKey, nil
}

return nil, errors.New("unsupported key format or invalid password")
return nil, ErrUnsupportedKey
}

// GetKeyFromFile reads a private key from a file and parses it using ParsePrivateKey.
Expand All @@ -265,16 +264,16 @@ func GetKeyFromFile(
inputReader Reader,
) (crypto.PrivateKey, error) {
if keyFilePath == emptyString {
return nil, errors.New("empty string provided as keyFilePath")
return nil, &EmptyArgError{Name: "keyFilePath"}
}

if inputReader == nil {
return nil, errors.New("nil Reader provided")
return nil, ErrNilReader
}

keyPEM, err := inputReader.ReadFile(keyFilePath)
if err != nil {
return nil, err
return nil, fmt.Errorf("error reading private key file: %w", err)
}

key, err := ParsePrivateKey(
Expand Down
Loading
Loading