• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pomerium / pomerium / 19183140028

07 Nov 2025 10:38PM UTC coverage: 55.476% (-0.6%) from 56.094%
19183140028

push

github

web-flow
feat: ssh authorization code flow (#5873)

## Summary

Implements the oauth authorization code flow for native SSH connections
in pomerium.

## Related issues


[ENG-3056](https://linear.app/pomerium/issue/ENG-3056/ssh-authorization-code-flow-initial-implementation)

The issue has a comment explaining some known limitations.

## User Explanation

Allows user to use native SSH without requiring IDPs to support the
device code flow. Native ssh now uses the Authorization code flow, which
most if not all IDPs support - notably, any existing IDP configuration
for non-native ssh Pomerium should now work with native ssh.

## Checklist

- [X] reference any related issues
- [X] updated unit tests
- [X] add appropriate label (`enhancement`, `bug`, `breaking`,
`dependencies`, `ci`)
- [X] ready for review

---------

Co-authored-by: Joe Kralicky <joekralicky@gmail.com>

284 of 1065 new or added lines in 19 files covered. (26.67%)

25 existing lines in 5 files now uncovered.

28737 of 51801 relevant lines covered (55.48%)

94.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/ssh/code/revoker.go
1
package code
2

3
import (
4
        "context"
5

6
        "github.com/cenkalti/backoff/v4"
7
        "google.golang.org/grpc/codes"
8
        "google.golang.org/grpc/status"
9
        "google.golang.org/protobuf/types/known/timestamppb"
10

11
        "github.com/pomerium/pomerium/pkg/grpc/databroker"
12
)
13

14
type revoker struct {
15
        client databroker.DataBrokerServiceClient
16
}
17

18
var _ Revoker = (*revoker)(nil)
19

NEW
20
func NewRevoker(client databroker.DataBrokerServiceClient) Revoker {
×
NEW
21
        return &revoker{
×
NEW
22
                client: client,
×
NEW
23
        }
×
NEW
24
}
×
25

NEW
26
func (r *revoker) RevokeCode(ctx context.Context, codeID CodeID) error {
×
NEW
27
        rec, err := r.client.Get(ctx, &databroker.GetRequest{
×
NEW
28
                Type: "type.googleapis.com/session.SessionBindingRequest",
×
NEW
29
                Id:   string(codeID),
×
NEW
30
        })
×
NEW
31

×
NEW
32
        if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
×
NEW
33
                return nil
×
NEW
34
        } else if err != nil {
×
NEW
35
                return err
×
NEW
36
        }
×
37

NEW
38
        if rec.GetRecord().GetDeletedAt() != nil {
×
NEW
39
                return nil
×
NEW
40
        }
×
41

NEW
42
        rec.Record.DeletedAt = timestamppb.Now()
×
NEW
43

×
NEW
44
        _, err = r.client.Patch(ctx, &databroker.PatchRequest{
×
NEW
45
                Records: []*databroker.Record{
×
NEW
46
                        rec.Record,
×
NEW
47
                },
×
NEW
48
        })
×
NEW
49
        return err
×
50
}
51

NEW
52
func (r *revoker) RevokeSessionBinding(ctx context.Context, bindingID BindingID) error {
×
NEW
53
        sbResp, err := r.client.Get(ctx, &databroker.GetRequest{
×
NEW
54
                Type: "type.googleapis.com/session.SessionBinding",
×
NEW
55
                Id:   string(bindingID),
×
NEW
56
        })
×
NEW
57

×
NEW
58
        if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
×
NEW
59
                return nil
×
NEW
60
        }
×
NEW
61
        if err != nil {
×
NEW
62
                return err
×
NEW
63
        }
×
NEW
64
        if sbResp.Record.GetDeletedAt() != nil {
×
NEW
65
                return nil
×
NEW
66
        }
×
NEW
67
        rec := sbResp.Record
×
NEW
68
        rec.DeletedAt = timestamppb.Now()
×
NEW
69
        _, err = r.client.Patch(ctx, &databroker.PatchRequest{
×
NEW
70
                Records: []*databroker.Record{
×
NEW
71
                        rec,
×
NEW
72
                },
×
NEW
73
        })
×
NEW
74
        return err
×
75
}
76

NEW
77
func (r *revoker) RevokeSessionBindingBySession(ctx context.Context, sessionID string) ([]*databroker.Record, error) {
×
NEW
78
        b := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
×
NEW
79
        recs, err := backoff.RetryWithData(func() ([]*databroker.Record, error) {
×
NEW
80
                return getSessionBindingBySession(ctx, r.client, sessionID)
×
NEW
81
        }, b)
×
NEW
82
        if err != nil {
×
NEW
83
                return nil, err
×
NEW
84
        }
×
NEW
85
        if len(recs) == 0 {
×
NEW
86
                return []*databroker.Record{}, nil
×
NEW
87
        }
×
NEW
88
        for _, rec := range recs {
×
NEW
89
                rec.DeletedAt = timestamppb.Now()
×
NEW
90
        }
×
NEW
91
        _, err = r.client.Patch(ctx, &databroker.PatchRequest{
×
NEW
92
                Records: recs,
×
NEW
93
        })
×
NEW
94
        return recs, err
×
95
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc