diff --git a/cmd/support-scheduler/res/configuration.toml b/cmd/support-scheduler/res/configuration.toml index 3bb6bb2050..1ebdeaec3e 100644 --- a/cmd/support-scheduler/res/configuration.toml +++ b/cmd/support-scheduler/res/configuration.toml @@ -26,6 +26,8 @@ Name = "scheduler" Path = "/api/v2/event/age/604800000000000" # Remove events older than 7 days Interval = "midnight" AdminState = "UNLOCKED" + # AuthMethod = JWT degrades to no auth in security-disabled EdgeX + AuthMethod = "JWT" [MessageBus] [MessageBus.Optional] diff --git a/internal/pkg/utils/restaddress.go b/internal/pkg/utils/restaddress.go index 4a4b211dea..0064988989 100644 --- a/internal/pkg/utils/restaddress.go +++ b/internal/pkg/utils/restaddress.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" + "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces" "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger" "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" @@ -25,7 +26,9 @@ var methods = map[string]struct{}{ } // SendRequestWithRESTAddress sends request with REST address -func SendRequestWithRESTAddress(lc logger.LoggingClient, content string, contentType string, address models.RESTAddress) (res string, err errors.EdgeX) { +func SendRequestWithRESTAddress(lc logger.LoggingClient, content string, contentType string, + address models.RESTAddress, jwtSecretProvider interfaces.AuthenticationInjector) (res string, err errors.EdgeX) { + executingUrl := getUrlStr(address) req, err := getHttpRequest(address.HTTPMethod, executingUrl, content, contentType) @@ -33,6 +36,12 @@ func SendRequestWithRESTAddress(lc logger.LoggingClient, content string, content return "", errors.NewCommonEdgeX(errors.KindServerError, "fail to create http request", err) } + if jwtSecretProvider != nil { + if err2 := jwtSecretProvider.AddAuthenticationData(req); err2 != nil { + return "", errors.NewCommonEdgeXWrapper(err2) + } + } + client := &http.Client{} res, err = sendRequestAndGetResponse(client, req) if err != nil { diff --git a/internal/support/notifications/application/channel/sender.go b/internal/support/notifications/application/channel/sender.go index 68daf7e5a9..6571be4711 100644 --- a/internal/support/notifications/application/channel/sender.go +++ b/internal/support/notifications/application/channel/sender.go @@ -39,7 +39,9 @@ func (sender *RESTSender) Send(notification models.Notification, address models. if !ok { return "", errors.NewCommonEdgeX(errors.KindContractInvalid, "fail to cast Address to RESTAddress", nil) } - return utils.SendRequestWithRESTAddress(lc, notification.Content, notification.ContentType, restAddress) + // NOTE: Not currently passing an AuthenticationInjector here; + // no current notifications are calling EdgeX services + return utils.SendRequestWithRESTAddress(lc, notification.Content, notification.ContentType, restAddress, nil) } // EmailSender is the implementation of the interfaces.ChannelSender, which is used to send the notifications via email diff --git a/internal/support/scheduler/application/intervalaction.go b/internal/support/scheduler/application/intervalaction.go index c6f5da1348..a94615666c 100644 --- a/internal/support/scheduler/application/intervalaction.go +++ b/internal/support/scheduler/application/intervalaction.go @@ -164,6 +164,7 @@ func LoadIntervalActionToSchedulerManager(dic *di.Container) errors.EdgeX { Content: configuration.IntervalActions[i].Content, ContentType: configuration.IntervalActions[i].ContentType, AdminState: configuration.IntervalActions[i].AdminState, + AuthMethod: configuration.IntervalActions[i].AuthMethod, } validateErr := common.Validate(dto) if validateErr != nil { diff --git a/internal/support/scheduler/application/scheduler/manager.go b/internal/support/scheduler/application/scheduler/manager.go index 447824a97b..f3d30a2258 100644 --- a/internal/support/scheduler/application/scheduler/manager.go +++ b/internal/support/scheduler/application/scheduler/manager.go @@ -13,6 +13,11 @@ import ( "github.com/edgexfoundry/edgex-go/internal/support/scheduler/config" "github.com/edgexfoundry/edgex-go/internal/support/scheduler/infrastructure/interfaces" + bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/secret" + + clientInterfaces "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces" + "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger" "github.com/edgexfoundry/go-mod-core-contracts/v3/common" "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" @@ -30,10 +35,11 @@ type manager struct { executorQueue *queue.Queue intervalToExecutorMap map[string]*Executor actionToIntervalMap map[string]string + secretProvider bootstrapInterfaces.SecretProvider } // NewManager creates a new scheduler manager for running the interval job -func NewManager(lc logger.LoggingClient, config *config.ConfigurationStruct) interfaces.SchedulerManager { +func NewManager(lc logger.LoggingClient, config *config.ConfigurationStruct, secretProvider bootstrapInterfaces.SecretProvider) interfaces.SchedulerManager { return &manager{ ticker: time.NewTicker(time.Duration(config.ScheduleIntervalTime) * time.Millisecond), lc: lc, @@ -41,6 +47,7 @@ func NewManager(lc logger.LoggingClient, config *config.ConfigurationStruct) int executorQueue: queue.New(), intervalToExecutorMap: make(map[string]*Executor), actionToIntervalMap: make(map[string]string), + secretProvider: secretProvider, } } @@ -134,7 +141,16 @@ func (m *manager) executeAction(action models.IntervalAction) errors.EdgeX { if !ok { return errors.NewCommonEdgeX(errors.KindContractInvalid, "fail to cast Address to RESTAddress", nil) } - _, err := utils.SendRequestWithRESTAddress(m.lc, action.Content, action.ContentType, restAddress) + + var jwtSecretProvider clientInterfaces.AuthenticationInjector + // Awaiting change to go-mod-core-contracts + if action.AuthMethod == config.AuthMethodJWT { + jwtSecretProvider = secret.NewJWTSecretProvider(m.secretProvider) + } else { + jwtSecretProvider = secret.NewJWTSecretProvider(nil) + } + + _, err := utils.SendRequestWithRESTAddress(m.lc, action.Content, action.ContentType, restAddress, jwtSecretProvider) if err != nil { m.lc.Errorf("fail to send request with RESTAddress, err: %v", err) } diff --git a/internal/support/scheduler/application/scheduler/manager_test.go b/internal/support/scheduler/application/scheduler/manager_test.go index 816217248f..d7735af3b6 100644 --- a/internal/support/scheduler/application/scheduler/manager_test.go +++ b/internal/support/scheduler/application/scheduler/manager_test.go @@ -25,6 +25,6 @@ func TestNewManager(t *testing.T) { IntervalActions: nil, ScheduleIntervalTime: 500, } - manager := NewManager(lc, config) + manager := NewManager(lc, config, nil) require.NotNil(t, manager) } diff --git a/internal/support/scheduler/config/config.go b/internal/support/scheduler/config/config.go index 3d32ac20ca..3fe2a647d2 100644 --- a/internal/support/scheduler/config/config.go +++ b/internal/support/scheduler/config/config.go @@ -82,8 +82,15 @@ type IntervalActionInfo struct { ContentType string // Administrative state (LOCKED/UNLOCKED) AdminState string + // AuthMethod indicates how to authenticate the outbound URL -- "none" (default) or "jwt" + AuthMethod string } +const ( + AuthMethodNone = "NONE" + AuthMethodJWT = "JWT" +) + // URI constructs a URI from the protocol, host and port and returns that as a string. func (e IntervalActionInfo) URL() string { return fmt.Sprintf("%s://%s:%v", e.Protocol, e.Host, e.Port) diff --git a/internal/support/scheduler/init.go b/internal/support/scheduler/init.go index 48500dd78b..0612363cab 100644 --- a/internal/support/scheduler/init.go +++ b/internal/support/scheduler/init.go @@ -49,10 +49,11 @@ func (b *Bootstrap) BootstrapHandler(ctx context.Context, wg *sync.WaitGroup, _ LoadRestRoutes(b.router, dic, b.serviceName) lc := bootstrapContainer.LoggingClientFrom(dic.Get) + secretProvider := bootstrapContainer.SecretProviderFrom(dic.Get) configuration := container.ConfigurationFrom(dic.Get) // V2 Scheduler - schedulerManager := scheduler.NewManager(lc, configuration) + schedulerManager := scheduler.NewManager(lc, configuration, secretProvider) dic.Update(di.ServiceConstructorMap{ container.SchedulerManagerName: func(get di.Get) interface{} { return schedulerManager