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
3 changes: 3 additions & 0 deletions backend/pkg/identity/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package identity
import (
"net/http"
"regexp"
"strings"

"github.com/gin-gonic/gin"

Expand Down Expand Up @@ -69,6 +70,7 @@ func middlewareWithLogger(c *gin.Context) {
if err != nil {
goto exitUnauthorized
}
idty.FeatureFlags = strings.Fields(c.Request.Header.Get("X-Men-Features"))
ctx = WithContext(ctx, &idty)
if idty.IsDevice {
key = "device_id"
Expand Down Expand Up @@ -107,6 +109,7 @@ func middlewareBase(c *gin.Context) {
if err != nil {
goto exitUnauthorized
}
idty.FeatureFlags = strings.Fields(c.Request.Header.Get("X-Men-Features"))
ctx = WithContext(ctx, &idty)
c.Request = c.Request.WithContext(ctx)
return
Expand Down
41 changes: 41 additions & 0 deletions backend/pkg/identity/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func TestGinMiddleware(t *testing.T) {
Tenant: "123456789012345678901234",
IsUser: true,
Plan: "professional",

FeatureFlags: []string{},
}
actual := FromContext(ctx)
assert.EqualValues(t, expected, actual)
Expand Down Expand Up @@ -117,6 +119,8 @@ func TestGinMiddleware(t *testing.T) {
Subject: "3e955f9d-53bf-47d6-a182-ff27b2c96282",
Tenant: "123456789012345678901234",
IsDevice: true,

FeatureFlags: []string{},
}
actual := FromContext(ctx)
assert.EqualValues(t, expected, actual)
Expand Down Expand Up @@ -156,6 +160,43 @@ func TestGinMiddleware(t *testing.T) {
expected := &Identity{
Subject: "3e955f9d-53bf-47d6-a182-ff27b2c96282",
Tenant: "123456789012345678901234",

FeatureFlags: []string{},
}
actual := FromContext(ctx)
assert.EqualValues(t, expected, actual)
logger := log.FromContext(ctx)
assert.Empty(t, logger.Entry.Data)
},
}, {
Name: "ok, with featureFlag",
Request: func() *http.Request {
req, _ := http.NewRequest("GET",
"http://localhost/api/management/v1/test?foo=bar",
nil,
)
req.Header.Set("Authorization",
"Bearer "+makeFakeAuth(Identity{
Subject: "3e955f9d-53bf-47d6-a182-ff27b2c96282",
Tenant: "123456789012345678901234",
}),
)
req.Header.Set("X-Men-Features", "devauth:fancypants")
return req
}(),
Options: NewMiddlewareOptions().
SetPathRegex("^/api/management/v1/test$").
SetUpdateLogger(false),

Validator: func(t *testing.T,
w *httptest.ResponseRecorder, req *http.Request,
) {
ctx := req.Context()
expected := &Identity{
Subject: "3e955f9d-53bf-47d6-a182-ff27b2c96282",
Tenant: "123456789012345678901234",

FeatureFlags: []string{"devauth:fancypants"},
}
actual := FromContext(ctx)
assert.EqualValues(t, expected, actual)
Expand Down
15 changes: 8 additions & 7 deletions backend/pkg/identity/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import (
)

type Identity struct {
Subject string `json:"sub" valid:"required"`
Tenant string `json:"mender.tenant,omitempty"`
IsUser bool `json:"mender.user,omitempty"`
IsDevice bool `json:"mender.device,omitempty"`
Plan string `json:"mender.plan,omitempty"`
Addons []addons.Addon `json:"mender.addons,omitempty"`
Trial bool `json:"mender.trial"`
Subject string `json:"sub" valid:"required"`
Tenant string `json:"mender.tenant,omitempty"`
IsUser bool `json:"mender.user,omitempty"`
IsDevice bool `json:"mender.device,omitempty"`
Plan string `json:"mender.plan,omitempty"`
Addons []addons.Addon `json:"mender.addons,omitempty"`
Trial bool `json:"mender.trial"`
FeatureFlags []string `json:"-"`
}

// ExtractJWTFromHeader inspect the Authorization header for a Bearer token and
Expand Down
2 changes: 2 additions & 0 deletions backend/services/deviceauth/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ const (
// Max Request body size
SettingMaxRequestSize = "request_size_limit"
SettingMaxRequestSizeDefault = 1024 * 1024 // 1 MiB

SettingLegacyProvisionDevice = "legacy_provision_device"
)

var (
Expand Down
90 changes: 62 additions & 28 deletions backend/services/deviceauth/devauth/devauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"crypto/sha256"
"encoding/json"
"slices"
"strings"
"time"

Expand All @@ -34,6 +35,7 @@ import (

"github.com/mendersoftware/mender-server/services/deviceauth/access"
"github.com/mendersoftware/mender-server/services/deviceauth/cache"
dconfig "github.com/mendersoftware/mender-server/services/deviceauth/config"
"github.com/mendersoftware/mender-server/services/deviceauth/jwt"
"github.com/mendersoftware/mender-server/services/deviceauth/model"
"github.com/mendersoftware/mender-server/services/deviceauth/store"
Expand All @@ -47,6 +49,8 @@ const (
InventoryScopeSystem = "system"
)

const featureFlagProvision = "devauth:" + dconfig.SettingLegacyProvisionDevice

var (
ErrDevAuthUnauthorized = errors.New(MsgErrDevAuthUnauthorized)
ErrDevIdAuthIdMismatch = errors.New("dev auth: dev ID and auth ID mismatch")
Expand Down Expand Up @@ -135,6 +139,8 @@ type Config struct {
DefaultTenantToken string

HaveAddons bool

LegacyProvisionEvent bool
}

func NewDevAuth(d store.DataStore, co client.WorkflowsOtherAPI,
Expand Down Expand Up @@ -398,26 +404,41 @@ func (d *DevAuth) handlePreAuthDevice(
dev.Status = model.DevStatusAccepted
dev.AuthSets = append(dev.AuthSets, *aset)

if !dev.Provisioned {
reqId := requestid.FromContext(ctx)
var tenantID string
if idty := identity.FromContext(ctx); idty != nil {
tenantID = idty.Tenant
}
reqId := requestid.FromContext(ctx)
var (
tenantID string
legacyProvisionEvent bool
)
if idty := identity.FromContext(ctx); idty != nil {
tenantID = idty.Tenant
legacyProvisionEvent = d.config.LegacyProvisionEvent || // global
slices.Contains(idty.FeatureFlags, featureFlagProvision) // tenant
}

// submit device accepted job
//nolint:bodyclose
_, _, err := d.cOrch.StartWorkflow(ctx, "provision_device").
RequestBody(map[string]interface{}{
"request_id": reqId,
"device_id": aset.DeviceId,
"tenant_id": tenantID,
"device": dev,
"status": dev.Status,
}).Execute()
if err != nil {
return nil, errors.Wrap(err, "submit device provisioning job error")
workflowName := "provision_device"
if legacyProvisionEvent {
// NOTE: before Device.Provisioned flag was introduced, provision_device
// was always triggered.
if dev.Provisioned {
workflowName = "legacy_provision_iot_manager"
}
} else if dev.Provisioned {
// Device already provisioned, we're done...
return aset, nil
}

// submit device accepted job
//nolint:bodyclose
_, _, err = d.cOrch.StartWorkflow(ctx, workflowName).
RequestBody(map[string]interface{}{
"request_id": reqId,
"device_id": aset.DeviceId,
"tenant_id": tenantID,
"device": dev,
"status": dev.Status,
}).Execute()
if err != nil {
return nil, errors.Wrap(err, "submit device provisioning job error")
}
return aset, nil
}
Expand Down Expand Up @@ -871,7 +892,27 @@ func (d *DevAuth) AcceptDeviceAuth(ctx context.Context, device_id string, auth_i
return err
}

if dev.Provisioned {
workflowName := "provision_device"
var (
tenantID string
legacyProvisionEvent bool
)
if idty := identity.FromContext(ctx); idty != nil {
tenantID = idty.Tenant
legacyProvisionEvent = d.config.LegacyProvisionEvent || // global
slices.Contains(idty.FeatureFlags, featureFlagProvision) // tenant
}
if legacyProvisionEvent {
// Legacy behavior: trigger provision_device event whenever
// device transitions from pending status.
if dev.Status != model.DevStatusPending {
return nil
} else if dev.Provisioned {
// Use specialized provision job to only trigger
// user facing iot-manager event
workflowName = "legacy_provision_iot_manager"
}
} else if dev.Provisioned {
// Device already provisioned
// We're done...
return nil
Expand All @@ -881,18 +922,11 @@ func (d *DevAuth) AcceptDeviceAuth(ctx context.Context, device_id string, auth_i
aset.Status = model.DevStatusAccepted
dev.AuthSets = []model.AuthSet{*aset}

reqId := requestid.FromContext(ctx)

var tenantID string
if idty := identity.FromContext(ctx); idty != nil {
tenantID = idty.Tenant
}

// submit device accepted job
//nolint:bodyclose
_, _, err = d.cOrch.StartWorkflow(ctx, "provision_device").
_, _, err = d.cOrch.StartWorkflow(ctx, workflowName).
RequestBody(map[string]interface{}{
"request_id": reqId,
"request_id": requestid.FromContext(ctx),
"device_id": aset.DeviceId,
"tenant_id": tenantID,
"device": dev,
Expand Down
5 changes: 3 additions & 2 deletions backend/services/deviceauth/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ func RunServer(c config.Reader) error {
inv.DeviceInventoryInternalAPIAPI,
jwtHandler,
devauth.Config{
Issuer: c.GetString(dconfig.SettingJWTIssuer),
ExpirationTime: int64(c.GetInt(dconfig.SettingJWTExpirationTimeout)),
Issuer: c.GetString(dconfig.SettingJWTIssuer),
ExpirationTime: int64(c.GetInt(dconfig.SettingJWTExpirationTimeout)),
LegacyProvisionEvent: c.GetBool(dconfig.SettingLegacyProvisionDevice),
})

if jwtFallbackHandler != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "legacy_provision_iot_manager",
"description": "Provision device (backward compat).",
"version": 1,
"tasks": [
{
"name": "provision_iot-manager",
"type": "http",
"retries": 3,
"retryDelaySeconds": 5,
"http": {
"uri": "http://${env.IOT_MANAGER_ADDR|mender-iot-manager:8080}/api/internal/v1/iot-manager/tenants/${encoding=url;workflow.input.tenant_id}/devices",
"method": "POST",
"contentType": "application/json",
"json": "${workflow.input.device}",
"headers": {
"X-MEN-RequestID": "${workflow.input.request_id}"
},
"connectionTimeOut": 8000,
"readTimeOut": 8000,
"statusCodes": [
201,
202,
204,
409
]
}
}
],
"inputParameters": [
"request_id",
"tenant_id",
"device"
]
}