Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: apply fixes for the 2.15 release#14540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
stirby merged 3 commits intorelease/2.15fromrelease-15-cherries
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes fromall 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
23 changes: 17 additions & 6 deletionsdocs/admin/notifications.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,9 +77,9 @@ can only be delivered to one method, and this method is configured globally with
[`CODER_NOTIFICATIONS_METHOD`](https://coder.com/docs/reference/cli/server#--notifications-method)
(default: `smtp`).

Enterprise customers canconfigured which method to use for each of the
supported[Events](#events); see the [Preferences](#preferences) section below
for moredetails.
Enterprise customers canconfigure which method to use for each of the supported
[Events](#events); see the [Preferences](#preferences) section below for more
details.

## SMTP (Email)

Expand All@@ -93,7 +93,7 @@ existing one.
| :------: | --------------------------------- | ------------------------------------- | ----------- | ----------------------------------------- | ------------- |
| ✔️ | `--notifications-email-from` | `CODER_NOTIFICATIONS_EMAIL_FROM` | `string` | The sender's address to use. | |
| ✔️ | `--notifications-email-smarthost` | `CODER_NOTIFICATIONS_EMAIL_SMARTHOST` | `host:port` | The SMTP relay to send messages through. | localhost:587 |
|-️ | `--notifications-email-hello` | `CODER_NOTIFICATIONS_EMAIL_HELLO` | `string` | The hostname identifying the SMTP server. | localhost |
|️ | `--notifications-email-hello` | `CODER_NOTIFICATIONS_EMAIL_HELLO` | `string` | The hostname identifying the SMTP server. | localhost |

**Authentication Settings:**

Expand DownExpand Up@@ -252,6 +252,18 @@ To pause sending notifications, execute
To resume sending notifications, execute
[`coder notifications resume`](https://coder.com/docs/reference/cli/notifications_resume).

## Troubleshooting

If notifications are not being delivered, use the following methods to
troubleshoot:

1. Ensure notifications are being added to the `notification_messages` table
2. Review any error messages in the `status_reason` column, should an error have
occurred
3. Review the logs (search for the term `notifications`) for diagnostic
information<br> _If you do not see any relevant logs, set
`CODER_VERBOSE=true` or `--verbose` to output debug logs_

## Internals

The notification system is built to operate concurrently in a single- or
Expand DownExpand Up@@ -288,5 +300,4 @@ messages._
- after `CODER_NOTIFICATIONS_MAX_SEND_ATTEMPTS` is exceeded, it transitions to
`permanent_failure`

Diagnostic messages will be saved in the `notification_messages` table and will
be logged, in the case of failure.
See [Troubleshooting](#troubleshooting) above for more details.
Binary file modifieddocs/images/admin/notification-states.png
View file
Open in desktop
Loading
Sorry, something went wrong.Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletionenterprise/coderd/coderdenttest/coderdenttest.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -174,6 +174,10 @@ type LicenseOptions struct {
// ExpiresAt is the time at which the license will hard expire.
// ExpiresAt should always be greater then GraceAt.
ExpiresAt time.Time
// NotBefore is the time at which the license becomes valid. If set to the
// zero value, the `nbf` claim on the license is set to 1 minute in the
// past.
NotBefore time.Time
Features license.Features
}

Expand All@@ -195,6 +199,13 @@ func (opts *LicenseOptions) Valid(now time.Time) *LicenseOptions {
return opts
}

func (opts *LicenseOptions) FutureTerm(now time.Time) *LicenseOptions {
opts.NotBefore = now.Add(time.Hour * 24)
opts.ExpiresAt = now.Add(time.Hour * 24 * 60)
opts.GraceAt = now.Add(time.Hour * 24 * 53)
return opts
}

func (opts *LicenseOptions) UserLimit(limit int64) *LicenseOptions {
return opts.Feature(codersdk.FeatureUserLimit, limit)
}
Expand DownExpand Up@@ -233,13 +244,16 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
if options.GraceAt.IsZero() {
options.GraceAt = time.Now().Add(time.Hour)
}
if options.NotBefore.IsZero() {
options.NotBefore = time.Now().Add(-time.Minute)
}

c := &license.Claims{
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.NewString(),
Issuer: "test@testing.test",
ExpiresAt: jwt.NewNumericDate(options.ExpiresAt),
NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Minute)),
NotBefore: jwt.NewNumericDate(options.NotBefore),
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Minute)),
},
LicenseExpires: jwt.NewNumericDate(options.GraceAt),
Expand Down
48 changes: 46 additions & 2 deletionsenterprise/coderd/license/license.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,13 @@ func LicensesEntitlements(
// 'Entitlements' group as a whole.
for _, license := range licenses {
claims, err := ParseClaims(license.JWT, keys)
var vErr *jwt.ValidationError
if xerrors.As(err, &vErr) && vErr.Is(jwt.ErrTokenNotValidYet) {
// The license isn't valid yet. We don't consider any entitlements contained in it, but
// it's also not an error. Just skip it silently. This can happen if an administrator
// uploads a license for a new term that hasn't started yet.
continue
}
if err != nil {
entitlements.Errors = append(entitlements.Errors,
fmt.Sprintf("Invalid license (%s) parsing claims: %s", license.UUID.String(), err.Error()))
Expand DownExpand Up@@ -287,6 +294,8 @@ var (
ErrInvalidVersion = xerrors.New("license must be version 3")
ErrMissingKeyID = xerrors.Errorf("JOSE header must contain %s", HeaderKeyID)
ErrMissingLicenseExpires = xerrors.New("license missing license_expires")
ErrMissingExp = xerrors.New("exp claim missing or not parsable")
ErrMultipleIssues = xerrors.New("license has multiple issues; contact support")
)

type Features map[codersdk.FeatureName]int64
Expand DownExpand Up@@ -336,7 +345,7 @@ func ParseRaw(l string, keys map[string]ed25519.PublicKey) (jwt.MapClaims, error
return nil, xerrors.New("unable to parse Claims")
}

// ParseClaims validates adatabase.License record, and if valid, returns the claims. If
// ParseClaims validates araw JWT, and if valid, returns the claims. If
// unparsable or invalid, it returns an error
func ParseClaims(rawJWT string, keys map[string]ed25519.PublicKey) (*Claims, error) {
tok, err := jwt.ParseWithClaims(
Expand All@@ -348,18 +357,53 @@ func ParseClaims(rawJWT string, keys map[string]ed25519.PublicKey) (*Claims, err
if err != nil {
return nil, err
}
if claims, ok := tok.Claims.(*Claims); ok && tok.Valid {
return validateClaims(tok)
}

func validateClaims(tok *jwt.Token) (*Claims, error) {
if claims, ok := tok.Claims.(*Claims); ok {
if claims.Version != uint64(CurrentVersion) {
return nil, ErrInvalidVersion
}
if claims.LicenseExpires == nil {
return nil, ErrMissingLicenseExpires
}
if claims.ExpiresAt == nil {
return nil, ErrMissingExp
}
return claims, nil
}
return nil, xerrors.New("unable to parse Claims")
}

// ParseClaimsIgnoreNbf validates a raw JWT, but ignores `nbf` claim. If otherwise valid, it returns
// the claims. If unparsable or invalid, it returns an error. Ignoring the `nbf` (not before) is
// useful to determine if a JWT _will_ become valid at any point now or in the future.
func ParseClaimsIgnoreNbf(rawJWT string, keys map[string]ed25519.PublicKey) (*Claims, error) {
tok, err := jwt.ParseWithClaims(
rawJWT,
&Claims{},
keyFunc(keys),
jwt.WithValidMethods(ValidMethods),
)
var vErr *jwt.ValidationError
if xerrors.As(err, &vErr) {
// zero out the NotValidYet error to check if there were other problems
vErr.Errors = vErr.Errors & (^jwt.ValidationErrorNotValidYet)
if vErr.Errors != 0 {
// There are other errors besides not being valid yet. We _could_ go
// through all the jwt.ValidationError bits and try to work out the
// correct error, but if we get here something very strange is
// going on so let's just return a generic error that says to get in
// touch with our support team.
return nil, ErrMultipleIssues
}
} else if err != nil {
return nil, err
}
return validateClaims(tok)
}

func keyFunc(keys map[string]ed25519.PublicKey) func(*jwt.Token) (interface{}, error) {
return func(j *jwt.Token) (interface{}, error) {
keyID, ok := j.Header[HeaderKeyID].(string)
Expand Down
19 changes: 19 additions & 0 deletionsenterprise/coderd/license/license_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -826,6 +826,25 @@ func TestLicenseEntitlements(t *testing.T) {
assert.True(t, entitlements.Features[codersdk.FeatureCustomRoles].Enabled, "custom-roles enabled for premium")
},
},
{
Name: "CurrentAndFuture",
Licenses: []*coderdenttest.LicenseOptions{
enterpriseLicense().UserLimit(100),
premiumLicense().UserLimit(200).FutureTerm(time.Now()),
},
Enablements: defaultEnablements,
AssertEntitlements: func(t *testing.T, entitlements codersdk.Entitlements) {
assertEnterpriseFeatures(t, entitlements)
assertNoErrors(t, entitlements)
assertNoWarnings(t, entitlements)
userFeature := entitlements.Features[codersdk.FeatureUserLimit]
assert.Equalf(t, int64(100), *userFeature.Limit, "user limit")
assert.Equal(t, codersdk.EntitlementNotEntitled,
entitlements.Features[codersdk.FeatureMultipleOrganizations].Entitlement)
assert.Equal(t, codersdk.EntitlementNotEntitled,
entitlements.Features[codersdk.FeatureCustomRoles].Entitlement)
},
},
}

for _, tc := range testCases {
Expand Down
32 changes: 11 additions & 21 deletionsenterprise/coderd/licenses.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,25 +86,7 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) {
return
}

rawClaims,err:=license.ParseRaw(addLicense.License,api.LicenseKeys)
iferr!=nil {
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
Message:"Invalid license",
Detail:err.Error(),
})
return
}
exp,ok:=rawClaims["exp"].(float64)
if!ok {
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
Message:"Invalid license",
Detail:"exp claim missing or not parsable",
})
return
}
expTime:=time.Unix(int64(exp),0)

claims,err:=license.ParseClaims(addLicense.License,api.LicenseKeys)
claims,err:=license.ParseClaimsIgnoreNbf(addLicense.License,api.LicenseKeys)
iferr!=nil {
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
Message:"Invalid license",
Expand DownExpand Up@@ -134,7 +116,7 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) {
dl,err:=api.Database.InsertLicense(ctx, database.InsertLicenseParams{
UploadedAt:dbtime.Now(),
JWT:addLicense.License,
Exp:expTime,
Exp:claims.ExpiresAt.Time,
UUID:id,
})
iferr!=nil {
Expand All@@ -160,7 +142,15 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) {
// don't fail the HTTP request, since we did write it successfully to the database
}

httpapi.Write(ctx,rw,http.StatusCreated,convertLicense(dl,rawClaims))
c,err:=decodeClaims(dl)
iferr!=nil {
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
Message:"Failed to decode database response",
Detail:err.Error(),
})
return
}
httpapi.Write(ctx,rw,http.StatusCreated,convertLicense(dl,c))
}

// postRefreshEntitlements forces an `updateEntitlements` call and publishes
Expand Down
48 changes: 48 additions & 0 deletionsenterprise/coderd/licenses_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import (
"context"
"net/http"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
Expand DownExpand Up@@ -82,6 +83,53 @@ func TestPostLicense(t *testing.T) {
t.Error("expected to get error status 400")
}
})

// Test a license that isn't yet valid, but will be in the future. We should allow this so that
// operators can upload a license ahead of time.
t.Run("NotYet", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
respLic := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
AccountType: license.AccountTypeSalesforce,
AccountID: "testing",
Features: license.Features{
codersdk.FeatureAuditLog: 1,
},
NotBefore: time.Now().Add(time.Hour),
GraceAt: time.Now().Add(2 * time.Hour),
ExpiresAt: time.Now().Add(3 * time.Hour),
})
assert.GreaterOrEqual(t, respLic.ID, int32(0))
// just a couple spot checks for sanity
assert.Equal(t, "testing", respLic.Claims["account_id"])
features, err := respLic.FeaturesClaims()
require.NoError(t, err)
assert.EqualValues(t, 1, features[codersdk.FeatureAuditLog])
})

// Test we still reject a license that isn't valid yet, but has other issues (e.g. expired
// before it starts).
t.Run("NotEver", func(t *testing.T) {
t.Parallel()
client, _ := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
lic := coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
AccountType: license.AccountTypeSalesforce,
AccountID: "testing",
Features: license.Features{
codersdk.FeatureAuditLog: 1,
},
NotBefore: time.Now().Add(time.Hour),
GraceAt: time.Now().Add(2 * time.Hour),
ExpiresAt: time.Now().Add(-time.Hour),
})
_, err := client.AddLicense(context.Background(), codersdk.AddLicenseRequest{
License: lic,
})
errResp := &codersdk.Error{}
require.ErrorAs(t, err, &errResp)
require.Equal(t, http.StatusBadRequest, errResp.StatusCode())
require.Contains(t, errResp.Detail, license.ErrMultipleIssues.Error())
})
}

func TestGetLicense(t *testing.T) {
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp