Skip to content
Open
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
6 changes: 6 additions & 0 deletions cmd/atenet/internal/router/extproc_in_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func TestParseActorRef(t *testing.T) {
want: resources.ActorRef{Atespace: "team-a", Name: "my-actor"},
wantErr: false,
},
{
name: "mixed-case host",
host: "My-Actor.Team-A.actors.resources.substrate.ate.dev",
want: resources.ActorRef{Atespace: "team-a", Name: "my-actor"},
wantErr: false,
},
{
name: "missing atespace label",
host: "my-actor.actors.resources.substrate.ate.dev",
Expand Down
24 changes: 23 additions & 1 deletion internal/resources/actorref.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,31 @@ func ActorRefFromActor(a *ateapipb.Actor) ActorRef {
}
}

// lowerASCII folds A-Z and leaves every other byte alone. Resource names are
// DNS-1123 labels, so ASCII is the whole alphabet here, and strings.ToLower
// would additionally fold characters outside it onto ASCII letters (the Kelvin
// sign U+212A onto "k", U+017F onto "s"), letting a non-ASCII host reach an
// actor under a spelling that is not its name.
func lowerASCII(s string) string {
b := []byte(s)
for i, c := range b {
if c >= 'A' && c <= 'Z' {
b[i] = c + ('a' - 'A')
}
}
return string(b)
}

// ParseActorDNSName parses a DNS name for a given actor.
//
// The name is folded to lower case first. DNS lookups are case-insensitive
// (RFC 4343), so a client that resolved "MyActor.MySpace.<suffix>" reaches us
// with that spelling preserved in the Host header, while actor and atespace
// names are always lower case. Folding keeps the request addressed to the same
// actor its DNS lookup resolved to instead of failing to parse.
func ParseActorDNSName(name string) (ActorRef, error) {
rest, found := strings.CutSuffix(strings.TrimSuffix(name, "."), "."+ActorDNSSuffix)
normalized := lowerASCII(strings.TrimSuffix(name, "."))
rest, found := strings.CutSuffix(normalized, "."+ActorDNSSuffix)
if !found {
return ActorRef{}, fmt.Errorf("invalid actor DNS name: must end with %s, got %q", ActorDNSSuffix, name)
}
Expand Down
10 changes: 8 additions & 2 deletions internal/resources/actorref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,14 @@ func TestParseActorDNSName(t *testing.T) {
{"valid trailing dot", "act-1.team-a.actors.resources.substrate.ate.dev.", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
{"wrong suffix", "act-1.team-a.example.com", ActorRef{}, true},
{"missing atespace", "act-1.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"invalid actor name", "ACT-1.team-a.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"invalid atespace", "act-1.TEAM.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"mixed-case actor name", "ACT-1.team-a.actors.resources.substrate.ate.dev", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
{"mixed-case atespace", "act-1.TEAM-A.actors.resources.substrate.ate.dev", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
{"mixed-case suffix", "act-1.team-a.Actors.Resources.Substrate.Ate.Dev", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
// strings.ToLower would fold the Kelvin sign onto "k" and hand "act-1k" a
// request addressed to a name no actor can have.
{"non-ASCII actor name", "act-1K.team-a.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"invalid actor name", "act_1.team-a.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"invalid atespace", "act-1.team_a.actors.resources.substrate.ate.dev", ActorRef{}, true},
{"host:port not accepted", "act-1.team-a.actors.resources.substrate.ate.dev:8080", ActorRef{}, true},
{"empty", "", ActorRef{}, true},
}
Expand Down