mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
ssh: improve 'whoami' format (#5714)
Old: ``` User ID: xxx Session ID: xxx Expires at: 2025-07-10 08:39:40.64992461 +0000 UTC Claims: aud: [xxx] email: [foo@bar.com] email_verified: [true] exp: [1.75212238e+09] family_name: [bar] given_name: [foo] iat: [1.75208638e+09] iss: [https://example.com] name: [Foo Bar] nickname: [foobar] picture: [https://example.com] sub: [xxx] updated_at: [2025-07-09T18:12:15.226Z] ``` New: ``` User ID: xxx Session ID: xxx Expires at: 2025-07-10 11:23:27.641004885 +0000 UTC (in 13h59m57s) Claims: aud: "xxx" email: "foo@bar.com" email_verified: true exp: 2025-07-10 07:23:27 +0000 UTC (in 9h59m56s) family_name: "bar" given_name: "foo" iat: 2025-07-09 21:23:27 +0000 UTC (4s ago) iss: "https://example.com" name: "Foo Bar" nickname: "foobar" picture: "https://example.com" sub: "xxx" updated_at: "2025-07-09T18:12:15.226Z" ```
This commit is contained in:
parent
88c7a6537a
commit
33abea3ea6
4 changed files with 64 additions and 32 deletions
|
@ -337,7 +337,7 @@ func (state state) GetUserInfo(users map[string]*User) *userInfo {
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
if u.Email == state.Email {
|
if u.Email == state.Email {
|
||||||
userInfo.Subject = u.ID
|
userInfo.Subject = u.ID
|
||||||
userInfo.Name = u.FirstName + " " + u.LastName
|
userInfo.Name = strings.TrimSpace(u.FirstName + " " + u.LastName)
|
||||||
userInfo.FamilyName = u.LastName
|
userInfo.FamilyName = u.LastName
|
||||||
userInfo.GivenName = u.FirstName
|
userInfo.GivenName = u.FirstName
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"text/template"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
oteltrace "go.opentelemetry.io/otel/trace"
|
oteltrace "go.opentelemetry.io/otel/trace"
|
||||||
|
@ -258,9 +259,48 @@ func (a *Auth) FormatSession(ctx context.Context, info StreamAuthInfo) ([]byte,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
err = sessionInfoTmpl.Execute(&b, session)
|
fmt.Fprintf(&b, "User ID: %s\n", session.UserId)
|
||||||
if err != nil {
|
fmt.Fprintf(&b, "Session ID: %s\n", sessionID)
|
||||||
return nil, err
|
fmt.Fprintf(&b, "Expires at: %s (in %s)\n",
|
||||||
|
session.ExpiresAt.AsTime().String(),
|
||||||
|
time.Until(session.ExpiresAt.AsTime()).Round(time.Second))
|
||||||
|
fmt.Fprintf(&b, "Claims:\n")
|
||||||
|
keys := make([]string, 0, len(session.Claims))
|
||||||
|
for key := range session.Claims {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
fmt.Fprintf(&b, " %s: ", key)
|
||||||
|
vs := session.Claims[key].AsSlice()
|
||||||
|
if len(vs) != 1 {
|
||||||
|
b.WriteRune('[')
|
||||||
|
}
|
||||||
|
if len(vs) == 1 {
|
||||||
|
switch key {
|
||||||
|
case "iat":
|
||||||
|
d, _ := vs[0].(float64)
|
||||||
|
t := time.Unix(int64(d), 0)
|
||||||
|
fmt.Fprintf(&b, "%s (%s ago)", t, time.Since(t).Round(time.Second))
|
||||||
|
case "exp":
|
||||||
|
d, _ := vs[0].(float64)
|
||||||
|
t := time.Unix(int64(d), 0)
|
||||||
|
fmt.Fprintf(&b, "%s (in %s)", t, time.Until(t).Round(time.Second))
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(&b, "%#v", vs[0])
|
||||||
|
}
|
||||||
|
} else if len(vs) > 1 {
|
||||||
|
for i, v := range vs {
|
||||||
|
fmt.Fprintf(&b, "%#v", v)
|
||||||
|
if i < len(vs)-1 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(vs) != 1 {
|
||||||
|
b.WriteRune(']')
|
||||||
|
}
|
||||||
|
b.WriteRune('\n')
|
||||||
}
|
}
|
||||||
return b.Bytes(), nil
|
return b.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
@ -375,13 +415,3 @@ func sshRequestFromStreamAuthInfo(info StreamAuthInfo) (*Request, error) {
|
||||||
LogOnlyIfDenied: info.InitialAuthComplete,
|
LogOnlyIfDenied: info.InitialAuthComplete,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionInfoTmpl = template.Must(template.New("session-info").Parse(`
|
|
||||||
User ID: {{.UserId}}
|
|
||||||
Session ID: {{.Id}}
|
|
||||||
Expires at: {{.ExpiresAt.AsTime}}
|
|
||||||
Claims:
|
|
||||||
{{- range $k, $v := .Claims }}
|
|
||||||
{{ $k }}: {{ $v.AsSlice }}
|
|
||||||
{{- end }}
|
|
||||||
`))
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -361,6 +362,7 @@ func TestFormatSession(t *testing.T) {
|
||||||
assert.ErrorContains(t, err, "invalid public key fingerprint")
|
assert.ErrorContains(t, err, "invalid public key fingerprint")
|
||||||
})
|
})
|
||||||
t.Run("ok", func(t *testing.T) {
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
exp := time.Now().Add(1 * time.Minute)
|
||||||
client := fakeDataBrokerServiceClient{
|
client := fakeDataBrokerServiceClient{
|
||||||
get: func(
|
get: func(
|
||||||
_ context.Context, in *databroker.GetRequest, _ ...grpc.CallOption,
|
_ context.Context, in *databroker.GetRequest, _ ...grpc.CallOption,
|
||||||
|
@ -379,7 +381,7 @@ func TestFormatSession(t *testing.T) {
|
||||||
Data: protoutil.NewAny(&session.Session{
|
Data: protoutil.NewAny(&session.Session{
|
||||||
Id: expectedID,
|
Id: expectedID,
|
||||||
UserId: "USER-ID",
|
UserId: "USER-ID",
|
||||||
ExpiresAt: ×tamppb.Timestamp{Seconds: 1750965358},
|
ExpiresAt: timestamppb.New(exp),
|
||||||
Claims: claims.ToPB(),
|
Claims: claims.ToPB(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -392,14 +394,14 @@ func TestFormatSession(t *testing.T) {
|
||||||
}
|
}
|
||||||
b, err := a.FormatSession(t.Context(), info)
|
b, err := a.FormatSession(t.Context(), info)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, string(b), `
|
assert.Regexp(t, `
|
||||||
User ID: USER-ID
|
User ID: USER-ID
|
||||||
Session ID: sshkey-SHA256:QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY
|
Session ID: sshkey-SHA256:QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY
|
||||||
Expires at: 2025-06-26 19:15:58 +0000 UTC
|
Expires at: .* \(in 1m0s\)
|
||||||
Claims:
|
Claims:
|
||||||
foo: [bar baz]
|
foo: \["bar", "baz"\]
|
||||||
quux: [42]
|
quux: 42
|
||||||
`)
|
`[1:], string(b))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -331,18 +331,18 @@ func (s *SSHTestSuite) TestWhoami() {
|
||||||
s.Regexp(s.executeTemplate(`
|
s.Regexp(s.executeTemplate(`
|
||||||
User ID: .*
|
User ID: .*
|
||||||
Session ID: sshkey-{{.PublicKeyFingerprint | quoteMeta}}
|
Session ID: sshkey-{{.PublicKeyFingerprint | quoteMeta}}
|
||||||
Expires at: .*
|
Expires at: .* \(in \d+h\d+m\d+s\)
|
||||||
Claims:
|
Claims:
|
||||||
aud: \[CLIENT_ID\]
|
aud: "CLIENT_ID"
|
||||||
email: \[{{.Email | quoteMeta}}\]
|
email: "{{.Email | quoteMeta}}"
|
||||||
exp: \[.*\]
|
exp: .* \(in \d+h\d+m\d+s\)
|
||||||
family_name: \[\]
|
family_name: ""
|
||||||
given_name: \[\]
|
given_name: ""
|
||||||
iat: \[.*\]
|
iat: .* \(\d+s ago\)
|
||||||
iss: \[https://mock-idp\..*\]
|
iss: "https://mock-idp\..*"
|
||||||
name: \[ \]
|
name: ""
|
||||||
sub: \[.*\]
|
sub: ".*"
|
||||||
`), string(output))
|
`[1:]), string(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSH(t *testing.T) {
|
func TestSSH(t *testing.T) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue