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
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ linters:
# proper noun: every sentinel in these files begins with "Codefly".
- linters:
- staticcheck
path: (^|/)(runtime_environment_file|mint|stream)\.go$
path: (^|/)(runtime_environment_file|mint|stream|verifier)\.go$
text: "ST1005:"
paths:
- .*\.pb\.go
Expand Down
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ see the skill below.
comes from `ReadAuthority` at boot. `WorkspaceValue` answers from that pin for
a pinned name, so a drift is an error rather than a reload — the process has
already minted a credential sealed to the old value.
- **There is one way to get a verifier.** Core's verifier is not re-exported:
every field of it has a usable zero value, so `NewVerifier` is the only
constructor and `PinnedVerifier` the only type here that verifies. The
endpoint, the fetch and the verifier are one operation — `KeySet` has one
constructor and `NewVerifier` takes nothing else — so an admitted location and
the keys in use are one fact. `ResolveKeySetEndpoint` is the host's transport
rule: plaintext reaches an in-cluster Service address only under
`internal-transport/mesh-protected`, compared literally, and loopback only on a
local run. A failed read of that setting is a refusal, not absence. No refusal
echoes a configured value. `scripts/security-posture.sh` re-audits both rules.
- **A receipt is written inside the transaction that commits its effect.** A
receipt written after the commit leaves a window where the effect exists and
the receipt does not, and a recovery landing there reads "no receipt" for an
Expand Down
112 changes: 112 additions & 0 deletions codefly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/codefly-dev/core/wool"
codefly "github.com/codefly-dev/sdk-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
Expand Down Expand Up @@ -501,3 +502,114 @@ func prepareDeclaredEndpointWorkspace(t *testing.T, module, service, endpoint, a
writeFile(t, filepath.Join(root, "modules", module, "services", service, "service.codefly.yaml"), "kind: service\nname: "+service+"\nversion: 0.0.0\nagent:\n kind: codefly:service\n name: rust\n version: 0.0.20\n publisher: codefly.dev\nendpoints:\n - name: "+endpoint+"\n api: "+api+"\n visibility: public\n")
t.Chdir(root)
}

// IsLocalRun is the Query's answer to the question the package-level IsLocal
// answers, as a method so a leaf module can require it through an interface
// instead of importing this one — workcontext.TransportSource does, to decide
// whether a plaintext hop to a loopback host is a local run.
//
// It is tested through the carrier rather than by asserting that it delegates,
// because what a consumer depends on is that the answer comes from the Codefly
// environment and not from a value the consumer chose.
func TestIsLocalRunAnswersFromTheEnvironment(t *testing.T) {
ctx := t.Context()

t.Run("a local environment", func(t *testing.T) {
t.Setenv(resources.EnvironmentPrefix, resources.LocalEnvironment().Name)

assert.True(t, codefly.For(ctx).IsLocalRun())
assert.Equal(t, codefly.IsLocal(), codefly.For(ctx).IsLocalRun())
})

t.Run("a deployed environment", func(t *testing.T) {
t.Setenv(resources.EnvironmentPrefix, "staging")

assert.False(t, codefly.For(ctx).IsLocalRun())
assert.Equal(t, codefly.IsLocal(), codefly.For(ctx).IsLocalRun())
})

t.Run("no environment at all is not a local run", func(t *testing.T) {
t.Setenv(resources.EnvironmentPrefix, "")

assert.False(t, codefly.For(ctx).IsLocalRun())
})
}

// WorkspaceValueIfSet separates the three answers WorkspaceValue flattens into
// two: the value is set, nothing carries it, or the lookup FAILED.
//
// A caller deciding a posture question — "may this hop be plaintext" — cannot
// live with the flattening. An unreadable delivery, or an authority-bearing
// value that has drifted from the one pinned at boot, read as "the operator did
// not assert it" is a failed read quietly answering a question it did not ask.
// Absence is an answer; a failed read is not.
func TestWorkspaceValueIfSetSeparatesAbsenceFromFailure(t *testing.T) {
ctx := t.Context()

t.Run("a value that is set", func(t *testing.T) {
t.Setenv("CODEFLY__WORKSPACE_CONFIGURATION__INTERNAL_TRANSPORT__MESH_PROTECTED", "true")

value, set, err := codefly.For(ctx).WorkspaceValueIfSet("internal-transport", "mesh-protected")

assert.NoError(t, err)
assert.True(t, set)
assert.Equal(t, "true", value)
})

t.Run("a value carried only by the secret namespace", func(t *testing.T) {
t.Setenv("CODEFLY__WORKSPACE_SECRET_CONFIGURATION__INTERNAL_TRANSPORT__MESH_PROTECTED", "true")

value, set, err := codefly.For(ctx).WorkspaceValueIfSet("internal-transport", "mesh-protected")

assert.NoError(t, err)
assert.True(t, set)
assert.Equal(t, "true", value)
})

t.Run("nothing carries it", func(t *testing.T) {
value, set, err := codefly.For(ctx).WorkspaceValueIfSet("internal-transport", "absent-here")

assert.NoError(t, err, "absence is an answer, not a failure")
assert.False(t, set)
assert.Empty(t, value)
})

t.Run("a delivery that cannot be read", func(t *testing.T) {
key := "CODEFLY__WORKSPACE_CONFIGURATION__INTERNAL_TRANSPORT__UNREADABLE"
t.Setenv(resources.FileCarrierKey(key), filepath.Join(t.TempDir(), "absent"))

_, set, err := codefly.For(ctx).WorkspaceValueIfSet("internal-transport", "unreadable")

assert.Error(t, err, "a delivery that is there and cannot be read is not absence")
assert.False(t, set)
})

t.Run("a delivery that is both inline and by file", func(t *testing.T) {
key := "CODEFLY__WORKSPACE_CONFIGURATION__INTERNAL_TRANSPORT__DOUBLED"
path := filepath.Join(t.TempDir(), "value")
require.NoError(t, os.WriteFile(path, []byte("true"), 0o600))
t.Setenv(key, "true")
t.Setenv(resources.FileCarrierKey(key), path)

_, set, err := codefly.For(ctx).WorkspaceValueIfSet("internal-transport", "doubled")

assert.Error(t, err)
assert.False(t, set)
})

// An authority-bearing value that has drifted is the decisive case: it is
// the one where the setting IS configured, the read fails, and reading the
// failure as absence would let a drifted value answer as "not asserted".
t.Run("an authority-bearing value that has drifted", func(t *testing.T) {
t.Setenv("CODEFLY__WORKSPACE_CONFIGURATION__MESH_PIN__MESH_PROTECTED", "true")
_, err := codefly.ReadAuthority(ctx, codefly.AuthorityValueName{Name: "mesh-pin", Key: "mesh-protected"})
require.NoError(t, err)

t.Setenv("CODEFLY__WORKSPACE_CONFIGURATION__MESH_PIN__MESH_PROTECTED", "false")

_, set, err := codefly.For(ctx).WorkspaceValueIfSet("mesh-pin", "mesh-protected")

assert.ErrorIs(t, err, codefly.ErrAuthorityValueChanged)
assert.False(t, set)
})
}
82 changes: 82 additions & 0 deletions for.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ func runtimeServiceConfigurationValue(ctx context.Context, candidates ...string)
return "", false, nil
}

// IsLocalRun reports whether the Codefly environment is local, as a METHOD so a
// leaf module can require it through an interface rather than importing this
// one.
//
// It is the same answer as the package-level IsLocal, and it exists because the
// alternative is worse. A plaintext-transport admission decision needs two facts
// — the composition's mesh assertion and whether this is a local run — and a
// caller that passed the second as a boolean would be holding a per-service
// opt-in that no address has to qualify for. With both on one object that this
// SDK answers from the carriers, the only way to say a hop is safe is for the
// composition to have said it. See workcontext.TransportSource.
func (q *Query) IsLocalRun() bool {
return IsLocal()
}

// WorkspaceConfiguration returns one non-secret workspace configuration value.
// Product services must use this API instead of depending on Codefly's environment
// variable encoding, which is an SDK/runtime implementation detail.
Expand Down Expand Up @@ -269,6 +284,73 @@ func (q *Query) WorkspaceValue(name string, key string) (string, error) {
return pinned, nil
}

// WorkspaceValueIfSet resolves a workspace value and distinguishes the three
// answers WorkspaceValue flattens into two: the value is set, the value is
// genuinely not configured, or the lookup FAILED.
//
// WorkspaceValue reports "not configured" and "the delivery could not be read"
// with the same error, because its callers want a value or nothing. A caller
// deciding a posture question cannot live with that: a setting that must be
// asserted before something is permitted has to treat an unreadable delivery,
// and an authority-bearing value that has drifted, as a refusal rather than as
// "the operator did not assert it". Absence is an answer; a failed read is not.
//
// So an unreadable file carrier, a value delivered both inline and by file, and
// ErrAuthorityValueChanged all come back as errors here, and only a value no
// namespace carries comes back as (", false, nil").
func (q *Query) WorkspaceValueIfSet(name string, key string) (string, bool, error) {
addressed := AuthorityValueName{Name: name, Key: key}.canonical()
pinned, isPinned := authorityPin(addressed)
value, found, err := q.workspaceValueIfSetLive(name, key)
if err != nil {
return "", false, err
}
if !isPinned {
return value, found, nil
}
if !found || strings.TrimSpace(value) == "" {
return "", false, fmt.Errorf("%w: %s is no longer configured", ErrAuthorityValueChanged, addressed)
}
if value != pinned {
return "", false, fmt.Errorf("%w: %s", ErrAuthorityValueChanged, addressed)
}
return pinned, true, nil
}

// workspaceValueIfSetLive reads the public namespace and then the secret one,
// returning a lookup failure rather than continuing past it. Continuing is what
// makes an unreadable value indistinguishable from an absent one.
func (q *Query) workspaceValueIfSetLive(name string, key string) (string, bool, error) {
for _, prefix := range []string{
resources.WorkspaceConfigurationPrefix,
resources.WorkspaceSecretConfigurationPrefix,
} {
value, err := q.workspaceConfigurationValue(prefix, name, key)
if err != nil {
if isWorkspaceValueAbsent(err) {
continue
}
return "", false, err
}
if value != "" {
return value, true, nil
}
}
return "", false, nil
}

// isWorkspaceValueAbsent reports whether err is the "nothing carries this name"
// answer rather than a failure to read something that is there.
func isWorkspaceValueAbsent(err error) bool {
return err != nil && strings.Contains(err.Error(), workspaceValueAbsentReason)
}

// workspaceValueAbsentReason is the text workspaceConfigurationValue returns
// when no namespace carries the name. It is matched here because the lookup
// reports absence with a wool error rather than a sentinel; a sentinel is the
// better shape and this is where it would be read from if one lands.
const workspaceValueAbsentReason = "no workspace configuration value for"

func (q *Query) workspaceValueLive(name string, key string) (string, error) {
if value, err := q.WorkspaceConfiguration(name, key); err == nil && value != "" {
return value, nil
Expand Down
22 changes: 18 additions & 4 deletions scripts/importsof/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,24 @@ func run() error {
if path == "" {
continue
}
// ImportsOnly, so a file whose BODY does not compile is still read for
// its imports: the sweep's subject is what a file reaches for, and a
// blob at some old ref need not build against today's dependencies.
syntax, err := parser.ParseFile(fileSet, path, nil, parser.ImportsOnly|parser.SkipObjectResolution)
// THE WHOLE FILE IS PARSED, not just its import block.
//
// It was ImportsOnly, on the reasoning that the sweep's subject is what
// a file reaches for and a blob at some old ref need not build against
// today's dependencies. The second half of that is still true and is
// why this is a SYNTAX parse and not a type-check: resolving a file at a
// two-year-old tag would need that tag's dependencies.
//
// The first half was wrong about what ImportsOnly does. It stops at the
// first non-import declaration, so a file whose body does not parse
// at all — an unfinished function, a missing expression — reads as a
// file with imports and no problem, and the sweep's own documented
// claim that "a file that does not parse is a failure, not a file with
// no imports" was false for every one of them. It also stops reading at
// the first declaration, so an import placed AFTER one is invisible:
// a file whose first declaration is a var and whose import block follows
// swept with no imports at all. Both are refused now.
syntax, err := parser.ParseFile(fileSet, path, nil, parser.SkipObjectResolution)
if err != nil {
return fmt.Errorf("parse %s: %w", path, err)
}
Expand Down
86 changes: 86 additions & 0 deletions scripts/security-posture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# The re-runnable check for each security-posture rule this repository owns.
#
# It exists because the catalogue's own commands could not fail. They counted
# lines — `go test … | grep -c '^--- PASS'` — which answers 0 for a PASSING run
# without -v, answers 1 when the parent test is the only unindented record
# whatever its subtests did, and answers 0 for a test that no longer exists at
# all. A check that cannot fail reports green forever, which is worse than no
# check: the invariant stops being audited and nothing says so.
#
# So this reads the EXIT STATUS, and separately requires every named test to
# have actually run. A rule's test being deleted, renamed or filtered out by a
# `-run` pattern that matches nothing is a failure here, not a silent pass.
#
# Usage:
# scripts/security-posture.sh # every rule
# scripts/security-posture.sh SP-WC-01 # one rule
#
# Exit 0 means the rule holds in this repository. Any other exit means it does
# not, or could not be checked.
set -euo pipefail

cd "$(dirname "$0")/.."

# Each rule names the module it is checked in and the tests that check it.
# A test named here must exist and must pass.
sp_wc_01_module="workcontext"
sp_wc_01_tests="TestDerivedContextNeverOutlivesParent"

sp_wc_05_module="workcontext"
sp_wc_05_tests="TestEveryVerificationPathIsPinned
TestNewVerifierRefusesAnIncompleteConfigurationByName
TestNewVerifierRefusesUnusableKeyMaterial
TestResolveKeySetEndpointAdmitsOnlyTheMeshContract
TestMeshProtectedComparesTheAssertionLiterally
TestMeshProtectedRefusesALookupFailureRatherThanReadingItAsAbsence
TestALookupFailureRefusesEveryKeySetEndpoint
TestAcquireKeySetFetchesFromTheAdmittedEndpoint
TestAVerifierHoldsOnlyTheKeysItsEndpointServed
TestNoRefusalEchoesTheURL
TestTheAssertionRefusalDoesNotEchoTheValue"

run_rule() {
local rule="$1" module="$2" tests="$3"
local pattern output status=0
pattern="^($(echo "$tests" | paste -sd'|' -))$"

echo "== $rule: $(echo "$tests" | wc -l | tr -d ' ') test(s) in ${module:-.}"
# -v so each test's own record is printed; the exit status is what decides,
# and the records are what prove the tests were not filtered away.
if ! output="$( (cd "${module:-.}" && go test -count=1 -v -run "$pattern" ./...) 2>&1 )"; then
status=1
fi
local name
while IFS= read -r name; do
[ -n "$name" ] || continue
if ! grep -qE "^--- PASS: ${name}(/|\$| )" <<<"$output"; then
echo "FAIL $rule: $name did not run and pass."
echo " A rule's test that was renamed, deleted or filtered out is a rule that"
echo " stopped being checked. That is the failure this line exists to report."
status=1
fi
done <<<"$tests"
if [ "$status" -ne 0 ]; then
echo "$output" | sed -n '/^--- FAIL\|^ --- FAIL\|^FAIL\|^# /p' | head -40
echo "FAIL $rule"
return 1
fi
echo "ok $rule"
}

rules="${1:-all}"
failed=0
case "$rules" in
SP-WC-01) run_rule SP-WC-01 "$sp_wc_01_module" "$sp_wc_01_tests" || failed=1 ;;
SP-WC-05) run_rule SP-WC-05 "$sp_wc_05_module" "$sp_wc_05_tests" || failed=1 ;;
all)
run_rule SP-WC-01 "$sp_wc_01_module" "$sp_wc_01_tests" || failed=1
run_rule SP-WC-05 "$sp_wc_05_module" "$sp_wc_05_tests" || failed=1
;;
*)
echo "unknown rule: $rules (known: SP-WC-01, SP-WC-05, all)" >&2
exit 2
;;
esac
exit "$failed"
21 changes: 21 additions & 0 deletions sweep_bypasses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,27 @@ func TestTheImportScannerReadsOneLineOnce(t *testing.T) {
})
}

// A FILE THAT DOES NOT PARSE IS A FAILURE, NOT A FILE WITH NO IMPORTS —
// which is what the scanner's own comment claimed while it read imports
// ONLY.
//
// parser.ImportsOnly stops at the first non-import declaration, so a body
// that does not parse at all was never looked at and the file swept clean;
// and an import placed AFTER a declaration was never read, so a file could
// carry one invisibly. Three shapes, each of which swept green before the
// scanner was changed to parse the whole file.
for name, probe := range map[string]string{
"an unfinished function body": "package x\n\nimport \"fmt\"\n\nfunc f() {\n\tfmt.Println(\n",
"a missing expression": "package x\n\nimport \"fmt\"\n\nvar _ = fmt.Sprint(\n",
"an import after a declaration": "package x\n\nvar first = 1\n\nimport \"crypto/ed25519\"\n\n" +
"var _ = ed25519.Sign\n",
} {
t.Run(name, func(t *testing.T) {
out, err := sweepOf(t, script, "unparsed.go", probe)
require.Error(t, err, "the sweep passed a file it could not parse:\n%s", out)
})
}

// THE DENY-BY-DEFAULT POLICY, THROUGH THE SCRIPT, IN TREE AND REF MODE.
//
// This is what had no test. The policy file is asserted not to LIST these
Expand Down
Loading
Loading