Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
35b2878
build(deps): bump golang.org/x/oauth2 from 0.35.0 to 0.36.0 (#909)
dependabot[bot] Mar 13, 2026
cf20b58
TPT-4298: Added PR title checking to lint workflow and new clean up r…
ezilber-akamai Mar 17, 2026
76d9391
TPT-4014: Redact sensitive data from logging (#906)
dawiddzhafarov Mar 20, 2026
dc00ea0
TPT-4320: Update FirewallID to use single pointer in LinodeInterfaceC…
zliang-akamai Mar 24, 2026
245707c
build(deps): bump slackapi/slack-github-action from 2.1.1 to 3.0.1 (#…
dependabot[bot] Mar 25, 2026
4a2a3ed
build(deps): bump actions/github-script from 7 to 8 (#918)
dependabot[bot] Mar 25, 2026
fb8c404
TPT-4318: Add @linode/dx-sdets to CODEOWNERS (#920)
lgarber-akamai Mar 26, 2026
69df217
TPT-3807: Added `DiskEncryption` field for LKE Node Pool creation (#917)
ezilber-akamai Mar 27, 2026
6a5e955
build(deps): bump golang.org/x/net from 0.51.0 to 0.52.0 (#911)
dependabot[bot] Mar 27, 2026
2b51e3c
TPT-4234: Fix firewall device for linode interfaces and add entities …
zliang-akamai Mar 27, 2026
09803f2
Cleanup LA notices for block storage encryption (#902)
zliang-akamai Mar 27, 2026
04c1c9e
Remove content field from list alert channels response (#925)
shkaruna Apr 8, 2026
ad25985
feat: add ACLP list entities method (#923)
shkaruna Apr 9, 2026
6950d17
build(deps): bump golang.org/x/net from 0.52.0 to 0.53.0 (#930)
dependabot[bot] Apr 20, 2026
9077a1f
build(deps): bump actions/github-script from 8 to 9 (#932)
dependabot[bot] Apr 20, 2026
b0f235e
Remove notes about interface availability from account settings and i…
zliang-akamai Apr 20, 2026
0937797
TPT-4224: Add OBJ global quota and usage (#899)
zliang-akamai May 4, 2026
190513d
build(deps): bump gopkg.in/ini.v1 from 1.67.1 to 1.67.2 (with linter …
dependabot[bot] May 5, 2026
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
30 changes: 29 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,35 @@ on:
jobs:
lint-tidy:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
# Enforce TPT-1234: prefix on PR titles, with the following exemptions:
# - PRs labeled 'dependencies' (e.g. Dependabot PRs)
# - PRs labeled 'hotfix' (urgent fixes that may not have a ticket)
# - PRs labeled 'community-contribution' (external contributors without TPT tickets)
# - PRs labeled 'ignore-for-release' (release PRs that don't need a ticket prefix)
- name: Validate PR Title
if: github.event_name == 'pull_request'
uses: amannn/action-semantic-pull-request@v6
with:
types: |
TPT-\d+
requireScope: false
# Override the default header pattern to allow hyphens and digits in the type
# (e.g. "TPT-4298: Description"). The default pattern only matches word
# characters (\w) which excludes hyphens.
headerPattern: '^([\w-]+):\s?(.*)$'
headerPatternCorrespondence: type, subject
ignoreLabels: |
dependencies
hotfix
community-contribution
ignore-for-release
env:
GITHUB_TOKEN: ${{ github.token }}

- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
Expand Down Expand Up @@ -99,7 +127,7 @@ jobs:

steps:
- name: Notify Slack
uses: slackapi/slack-github-action@v2.1.1
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/clean-release-notes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Clean Release Notes

on:
release:
types: [published]

jobs:
clean-release-notes:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Remove ticket prefixes from release notes
uses: actions/github-script@v9
with:
script: |
const release = context.payload.release;

let body = release.body;

if (!body) {
console.log("Release body empty, nothing to clean.");
return;
}

// Remove ticket prefixes like "TPT-1234: " or "TPT-1234:"
body = body.replace(/TPT-\d+:\s*/g, '');

await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
body: body
});

console.log("Release notes cleaned.");
2 changes: 1 addition & 1 deletion .github/workflows/integration_tests_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/github-script@v8
- uses: actions/github-script@v9
id: update-check-run
if: ${{ inputs.pull_request_number != '' && fromJson(steps.commit-hash.outputs.data).repository.pullRequest.headRef.target.oid == inputs.sha }}
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly_smoke_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

- name: Notify Slack
if: (success() || failure()) && github.repository == 'linode/linodego'
uses: slackapi/slack-github-action@v2.1.1
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-notify-slack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify Slack - Main Message
uses: slackapi/slack-github-action@v2.1.1
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @linode/dx
* @linode/dx @linode/dx-sdets
1 change: 0 additions & 1 deletion account_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ type AccountSettings struct {
// A string like "disabled", "suspended", or "active" describing the status of this account’s Object Storage service enrollment.
ObjectStorage *string `json:"object_storage"`

// NOTE: Interfaces for new linode setting may not currently be available to all users.
// A new configuration flag defines whether new Linodes can use Linode and/or legacy config interfaces.
InterfacesForNewLinodes InterfacesForNewLinodes `json:"interfaces_for_new_linodes"`

Expand Down
41 changes: 36 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ Body: {{.Body}}`))

var envDebug = false

// redactHeadersMap is a map of headers that should be redacted in logs,
// mapping the header name to its redacted value.
var redactHeadersMap = map[string]string{
"Authorization": "Bearer *******************************",
}

// Client is a wrapper around the Resty client
type Client struct {
resty *resty.Client
Expand Down Expand Up @@ -394,6 +400,19 @@ func (c *httpClient) applyAfterResponse(resp *http.Response) error {
return nil
}

// nolint:unused
func redactHeaders(headers http.Header) http.Header {
redacted := headers.Clone()

for header, redactedValue := range redactHeadersMap {
if headers.Get(header) != "" {
redacted.Set(header, redactedValue)
}
}

return redacted
}

// nolint:unused
func (c *httpClient) logRequest(req *http.Request, method, url string, bodyBuffer *bytes.Buffer) {
var reqBody string
Expand All @@ -408,7 +427,7 @@ func (c *httpClient) logRequest(req *http.Request, method, url string, bodyBuffe
err := reqLogTemplate.Execute(&logBuf, map[string]any{
"Method": method,
"URL": url,
"Headers": req.Header,
"Headers": redactHeaders(req.Header),
"Body": reqBody,
})
if err == nil {
Expand Down Expand Up @@ -456,7 +475,7 @@ func (c *httpClient) logResponse(resp *http.Response) (*http.Response, error) {

err := respLogTemplate.Execute(&logBuf, map[string]any{
"Status": resp.Status,
"Headers": resp.Header,
"Headers": redactHeaders(resp.Header),
"Body": respBody.String(),
})
if err == nil {
Expand Down Expand Up @@ -736,7 +755,7 @@ func (c *Client) addCachedResponse(endpoint string, response any, expiry *time.D
}

switch responseValue.Kind() {
case reflect.Ptr:
case reflect.Pointer:
// We want to automatically deref pointers to
// avoid caching mutable data.
entry.Data = responseValue.Elem().Interface()
Expand Down Expand Up @@ -827,10 +846,22 @@ func (c *Client) updateHostURL() {
)
}

func redactLogHeaders(header http.Header) {
for h, redactedValue := range redactHeadersMap {
if header.Get(h) != "" {
header.Set(h, redactedValue)
}
}
}

func (c *Client) enableLogSanitization() *Client {
c.resty.OnRequestLog(func(r *resty.RequestLog) error {
// masking authorization header
r.Header.Set("Authorization", "Bearer *******************************")
redactLogHeaders(r.Header)
return nil
})

c.resty.OnResponseLog(func(r *resty.ResponseLog) error {
redactLogHeaders(r.Header)
return nil
})
Comment on lines 857 to 866

Expand Down
99 changes: 99 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/jarcoal/httpmock"
"github.com/linode/linodego/internal/testutil"
"github.com/stretchr/testify/require"
)

func TestClient_SetAPIVersion(t *testing.T) {
Expand Down Expand Up @@ -703,3 +704,101 @@ func TestMonitorClient_SetAPIBasics(t *testing.T) {
t.Fatal(cmp.Diff(client.resty.BaseURL, expectedHost))
}
}

func TestRedactHeaders(t *testing.T) {
tests := []struct {
name string
headers http.Header
wantVal map[string]string
}{
{
name: "redacts authorization header",
headers: http.Header{
"Authorization": []string{"Bearer supersecrettoken"},
"Content-Type": []string{"application/json"},
},
wantVal: map[string]string{
"Authorization": redactHeadersMap["Authorization"],
"Content-Type": "application/json",
},
},
{
name: "leaves non-sensitive headers unchanged",
headers: http.Header{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
},
wantVal: map[string]string{
"Content-Type": "application/json",
"Accept": "application/json",
},
},
{
name: "handles empty headers",
headers: http.Header{},
wantVal: map[string]string{},
},
{
name: "does not mutate original headers",
headers: http.Header{
"Authorization": []string{"Bearer supersecrettoken"},
},
wantVal: map[string]string{
"Authorization": redactHeadersMap["Authorization"],
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
originalAuth := tt.headers.Get("Authorization")

result := redactHeaders(tt.headers)

// Verify expected values in result
for key, expectedVal := range tt.wantVal {
if got := result.Get(key); got != expectedVal {
t.Errorf("redactHeaders() header %q = %q, want %q", key, got, expectedVal)
}
}

// Verify original was not mutated
if tt.headers.Get("Authorization") != originalAuth {
t.Error("redactHeaders() mutated the original headers")
}
})
}
}

func TestEnableLogSanitization(t *testing.T) {
mockClient := testutil.CreateMockClient(t, NewClient)
mockClient.SetDebug(true)

plainTextToken := "supersecrettoken"
mockClient.SetToken(plainTextToken)

var logBuf bytes.Buffer
logger := testutil.CreateLogger()
logger.L.SetOutput(&logBuf)
mockClient.SetLogger(logger)

httpmock.RegisterResponder("GET", "=~.*",
httpmock.NewStringResponder(200, `{}`).HeaderSet(http.Header{
"Authorization": []string{"Bearer " + plainTextToken},
}))

_, err := mockClient.resty.R().Get("https://api.linode.com/v4/test")
require.NoError(t, err)

logOutput := logBuf.String()

// Verify token is not present in either request or response logs
if strings.Contains(logOutput, plainTextToken) {
t.Errorf("log output contains raw token %q, expected it to be redacted", plainTextToken)
}

// Verify Authorization header still appears (as redacted value) in request log
if !strings.Contains(logOutput, "Authorization") {
t.Error("expected Authorization header to appear in request log output")
}
}
9 changes: 5 additions & 4 deletions firewall_devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ func (device *FirewallDevice) UnmarshalJSON(b []byte) error {

// FirewallDeviceEntity contains information about a device associated with a Firewall
type FirewallDeviceEntity struct {
ID int `json:"id"`
Type FirewallDeviceType `json:"type"`
Label string `json:"label"`
URL string `json:"url"`
ID int `json:"id"`
Type FirewallDeviceType `json:"type"`
Label string `json:"label"`
URL string `json:"url"`
ParentEntity *FirewallDeviceEntity `json:"parent_entity"`
}

// ListFirewallDevices get devices associated with a given Firewall
Expand Down
21 changes: 11 additions & 10 deletions firewalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ const (

// A Firewall is a set of networking rules (iptables) applied to Devices with which it is associated
type Firewall struct {
ID int `json:"id"`
Label string `json:"label"`
Status FirewallStatus `json:"status"`
Tags []string `json:"tags,omitempty"`
Rules FirewallRuleSet `json:"rules"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
ID int `json:"id"`
Label string `json:"label"`
Status FirewallStatus `json:"status"`
Tags []string `json:"tags"`
Rules FirewallRuleSet `json:"rules"`
Entities []FirewallDeviceEntity `json:"entities"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
}

// DevicesCreationOptions fields are used when adding devices during the Firewall creation process.
type DevicesCreationOptions struct {
Linodes []int `json:"linodes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
Interfaces []int `json:"interfaces,omitempty"`
Linodes []int `json:"linodes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
LinodeInterfaces []int `json:"linode_interfaces,omitempty"`
}

Comment on lines +35 to 39
// FirewallCreateOptions fields are those accepted by CreateFirewall
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/go-querystring v1.2.0
github.com/jarcoal/httpmock v1.4.1
golang.org/x/net v0.51.0
golang.org/x/oauth2 v0.35.0
golang.org/x/text v0.34.0
gopkg.in/ini.v1 v1.67.1
golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.36.0
gopkg.in/ini.v1 v1.67.2
)

require (
Expand Down
Loading
Loading