github.com/Axway/agent-sdk@v1.1.101/pkg/agent/agent.go (about) 1 package agent 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/signal" 8 "runtime" 9 "runtime/pprof" 10 "strings" 11 "sync" 12 "syscall" 13 "time" 14 15 agentcache "github.com/Axway/agent-sdk/pkg/agent/cache" 16 "github.com/Axway/agent-sdk/pkg/agent/handler" 17 "github.com/Axway/agent-sdk/pkg/agent/resource" 18 "github.com/Axway/agent-sdk/pkg/agent/stream" 19 "github.com/Axway/agent-sdk/pkg/api" 20 "github.com/Axway/agent-sdk/pkg/apic" 21 apiV1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 22 "github.com/Axway/agent-sdk/pkg/apic/auth" 23 "github.com/Axway/agent-sdk/pkg/apic/provisioning" 24 "github.com/Axway/agent-sdk/pkg/authz/oauth" 25 "github.com/Axway/agent-sdk/pkg/cache" 26 "github.com/Axway/agent-sdk/pkg/config" 27 "github.com/Axway/agent-sdk/pkg/util" 28 hc "github.com/Axway/agent-sdk/pkg/util/healthcheck" 29 "github.com/Axway/agent-sdk/pkg/util/log" 30 ) 31 32 // AgentStatus - status for Agent resource 33 const ( 34 AgentRunning = "running" 35 AgentStopped = "stopped" 36 AgentFailed = "failed" 37 AgentUnhealthy = "unhealthy" 38 // CorsField - 39 CorsField = "cors" 40 // RedirectURLsField - 41 RedirectURLsField = "redirectURLs" 42 ) 43 44 // AgentResourceType - Holds the type for agent resource in Central 45 var AgentResourceType string 46 47 // APIValidator - Callback for validating the API 48 type APIValidator func(apiID, stageName string) bool 49 50 // ConfigChangeHandler - Callback for Config change event 51 type ConfigChangeHandler func() 52 53 // ShutdownHandler - function that the agent may implement to be called when a shutdown request is received 54 type ShutdownHandler func() 55 56 type agentData struct { 57 agentResourceManager resource.Manager 58 teamJob *centralTeamsCache 59 apicClient apic.Client 60 cfg config.CentralConfig 61 agentFeaturesCfg config.AgentFeaturesConfig 62 tokenRequester auth.PlatformTokenGetter 63 64 teamMap cache.Cache 65 cacheManager agentcache.Manager 66 apiValidator APIValidator 67 apiValidatorLock sync.Mutex 68 apiValidatorJobID string 69 configChangeHandler ConfigChangeHandler 70 agentResourceChangeHandler ConfigChangeHandler 71 agentShutdownHandler ShutdownHandler 72 proxyResourceHandler *handler.StreamWatchProxyHandler 73 isInitialized bool 74 75 provisioner provisioning.Provisioning 76 streamer *stream.StreamerClient 77 authProviderRegistry oauth.ProviderRegistry 78 79 publishingLock *sync.Mutex 80 ardLock sync.Mutex 81 82 status string 83 84 // profiling 85 profileDone chan struct{} 86 } 87 88 var agent agentData 89 var agentMutex sync.RWMutex 90 91 var logger log.FieldLogger 92 93 func init() { 94 logger = log.NewFieldLogger(). 95 WithPackage("sdk.agent"). 96 WithComponent("agent") 97 agent.proxyResourceHandler = handler.NewStreamWatchProxyHandler() 98 agentMutex = sync.RWMutex{} 99 agent.publishingLock = &sync.Mutex{} 100 } 101 102 // Initialize - Initializes the agent 103 func Initialize(centralCfg config.CentralConfig) error { 104 return InitializeWithAgentFeatures(centralCfg, config.NewAgentFeaturesConfiguration()) 105 } 106 107 // InitializeWithAgentFeatures - Initializes the agent with agent features 108 func InitializeWithAgentFeatures(centralCfg config.CentralConfig, agentFeaturesCfg config.AgentFeaturesConfig) error { 109 if agent.teamMap == nil { 110 agent.teamMap = cache.New() 111 } 112 113 err := checkRunningAgent() 114 if err != nil { 115 return err 116 } 117 118 err = config.ValidateConfig(agentFeaturesCfg) 119 if err != nil { 120 return err 121 } 122 agent.agentFeaturesCfg = agentFeaturesCfg 123 124 // validate the central config 125 if agentFeaturesCfg.ConnectionToCentralEnabled() { 126 err = config.ValidateConfig(centralCfg) 127 if err != nil { 128 return err 129 } 130 } 131 132 // check to confirm usagereporting.offline and agentfeatures.persistcache are not both set to true 133 if agentFeaturesCfg.PersistCacheEnabled() && centralCfg.GetUsageReportingConfig().IsOfflineMode() { 134 agentFeaturesCfg.SetPersistentCache(false) 135 } 136 137 // Only create the api map cache if it does not already exist 138 if agent.cacheManager == nil { 139 agent.cacheManager = agentcache.NewAgentCacheManager(centralCfg, agentFeaturesCfg.PersistCacheEnabled()) 140 } 141 142 setCentralConfig(centralCfg) 143 144 if centralCfg.GetUsageReportingConfig().IsOfflineMode() { 145 // Offline mode does not need more initialization 146 return nil 147 } 148 149 singleEntryFilter := []string{ 150 // Traceability host URL will be added by the traceability factory 151 centralCfg.GetURL(), 152 centralCfg.GetPlatformURL(), 153 centralCfg.GetAuthConfig().GetTokenURL(), 154 centralCfg.GetUsageReportingConfig().GetURL(), 155 } 156 api.SetConfigAgent( 157 GetUserAgent(), 158 centralCfg.GetSingleURL(), 159 singleEntryFilter) 160 161 if agentFeaturesCfg.ConnectionToCentralEnabled() { 162 err = handleCentralConfig(centralCfg) 163 if err != nil { 164 return err 165 } 166 } 167 168 if !agent.isInitialized { 169 err = handleInitialization() 170 if err != nil { 171 return err 172 } 173 } 174 175 agent.isInitialized = true 176 return nil 177 } 178 179 func handleCentralConfig(centralCfg config.CentralConfig) error { 180 err := initializeTokenRequester(centralCfg) 181 if err != nil { 182 return fmt.Errorf("could not authenticate to Amplify, please check your keys and key password") 183 } 184 185 // Init apic client when the agent starts, and on config change. 186 agent.apicClient = apic.New(centralCfg, agent.tokenRequester, agent.cacheManager) 187 188 if util.IsNotTest() { 189 err = initEnvResources(centralCfg, agent.apicClient) 190 if err != nil { 191 return err 192 } 193 } 194 195 if centralCfg.GetAgentName() != "" { 196 if agent.agentResourceManager == nil { 197 agent.agentResourceManager, err = resource.NewAgentResourceManager( 198 agent.cfg, agent.apicClient, agent.agentResourceChangeHandler, 199 ) 200 if err != nil { 201 return err 202 } 203 } else { 204 agent.agentResourceManager.OnConfigChange(agent.cfg, agent.apicClient) 205 } 206 } 207 208 return nil 209 } 210 211 func handleInitialization() error { 212 setupSignalProcessor() 213 // only do the periodic health check stuff if NOT in unit tests and running binary agents 214 if util.IsNotTest() { 215 hc.StartPeriodicHealthCheck() 216 } 217 218 if util.IsNotTest() && agent.agentFeaturesCfg.ConnectionToCentralEnabled() { 219 // if credentials can expire and need to be deprovisioned then start the credential checker 220 221 registerCredentialChecker() 222 223 startTeamACLCache() 224 } 225 226 return nil 227 } 228 229 // InitializeProfiling - setup the CPU and Memory profiling if options given 230 func InitializeProfiling(cpuProfile, memProfile string) { 231 if memProfile != "" || cpuProfile != "" { 232 setupProfileSignalProcessor(cpuProfile, memProfile) 233 } 234 } 235 236 func registerExternalIDPs() error { 237 if agent.cfg.GetAgentType() != config.TraceabilityAgent { 238 idPCfg := agent.agentFeaturesCfg.GetExternalIDPConfig() 239 if idPCfg == nil { 240 return nil 241 } 242 243 proxy := agent.cfg.GetProxyURL() 244 timeout := agent.cfg.GetClientTimeout() 245 for _, idp := range idPCfg.GetIDPList() { 246 tlsCfg := idp.GetTLSConfig() 247 if idp.GetTLSConfig() == nil { 248 tlsCfg = agent.cfg.GetTLSConfig() 249 } 250 err := registerCredentialProvider(idp, tlsCfg, proxy, timeout) 251 if err != nil { 252 return err 253 } 254 } 255 } 256 return nil 257 } 258 259 func registerCredentialProvider(idp config.IDPConfig, tlsCfg config.TLSConfig, proxyURL string, clientTimeout time.Duration) error { 260 err := GetAuthProviderRegistry().RegisterProvider(idp, tlsCfg, proxyURL, clientTimeout) 261 if err != nil { 262 logger. 263 WithField("name", idp.GetIDPName()). 264 WithField("type", idp.GetIDPType()). 265 WithField("metadata-url", idp.GetMetadataURL()). 266 Errorf("unable to register external IdP provider, any credential request to the IdP will not be processed. %s", err.Error()) 267 } 268 crdName := idp.GetIDPName() + " " + provisioning.OAuthIDPCRD 269 provider, err := GetAuthProviderRegistry().GetProviderByName(idp.GetIDPName()) 270 if err != nil { 271 return err 272 } 273 crd, err := NewOAuthCredentialRequestBuilder( 274 WithCRDName(crdName), 275 WithCRDForIDP(provider, provider.GetSupportedScopes()), 276 WithCRDOAuthSecret(), 277 WithCRDRequestSchemaProperty(getCorsSchemaPropertyBuilder()), 278 WithCRDRequestSchemaProperty(getAuthRedirectSchemaPropertyBuilder()), 279 WithCRDIsSuspendable(), 280 ).Register() 281 if err != nil { 282 logger. 283 WithField("title", idp.GetIDPTitle()). 284 Errorf("unable to create and register credential request definition. %s", err.Error()) 285 } else { 286 logger. 287 WithField("name", crd.Name). 288 WithField("title", idp.GetIDPTitle()). 289 Info("successfully created and registered credential request definition.") 290 } 291 return err 292 } 293 294 func getCorsSchemaPropertyBuilder() provisioning.PropertyBuilder { 295 // register the supported credential request defs 296 return provisioning.NewSchemaPropertyBuilder(). 297 SetName(CorsField). 298 SetLabel("Javascript Origins"). 299 IsArray(). 300 AddItem( 301 provisioning.NewSchemaPropertyBuilder(). 302 SetName("Origins"). 303 IsString()) 304 } 305 306 func getAuthRedirectSchemaPropertyBuilder() provisioning.PropertyBuilder { 307 return provisioning.NewSchemaPropertyBuilder(). 308 SetName(RedirectURLsField). 309 SetLabel("Redirect URLs"). 310 IsArray(). 311 AddItem( 312 provisioning.NewSchemaPropertyBuilder(). 313 SetName("URL"). 314 IsString()) 315 } 316 317 func initEnvResources(cfg config.CentralConfig, client apic.Client) error { 318 env, err := client.GetEnvironment() 319 if err != nil { 320 return err 321 } 322 323 cfg.SetAxwayManaged(env.Spec.AxwayManaged) 324 if cfg.GetEnvironmentID() == "" { 325 // need to save this ID for the traceability agent for later 326 cfg.SetEnvironmentID(env.Metadata.ID) 327 } 328 329 // Set up credential config from environment resource policies 330 cfg.GetCredentialConfig().SetShouldDeprovisionExpired(env.Policies.Credentials.Expiry.Action == "deprovision") 331 cfg.GetCredentialConfig().SetExpirationDays(int(env.Policies.Credentials.Expiry.Period)) 332 333 if cfg.GetTeamID() == "" { 334 team, err := client.GetCentralTeamByName(cfg.GetTeamName()) 335 if err != nil { 336 return err 337 } 338 339 cfg.SetTeamID(team.ID) 340 } 341 342 return nil 343 } 344 345 func checkRunningAgent() error { 346 // Check only on startup of binary agents 347 if !agent.isInitialized && util.IsNotTest() && !isRunningInDockerContainer() { 348 return hc.CheckIsRunning() 349 } 350 return nil 351 } 352 353 type TestOpt func(*testOpts) 354 355 type testOpts struct { 356 marketplace bool 357 agentType config.AgentType 358 } 359 360 // InitializeForTest - Initialize for test 361 func InitializeForTest(apicClient apic.Client, opts ...TestOpt) { 362 agent.apicClient = apicClient 363 tOpts := &testOpts{} 364 for _, o := range opts { 365 o(tOpts) 366 } 367 if agent.cfg == nil { 368 agent.cfg = config.NewTestCentralConfig(tOpts.agentType) 369 } 370 agent.cacheManager = agentcache.NewAgentCacheManager(agent.cfg, false) 371 agent.agentFeaturesCfg = &config.AgentFeaturesConfiguration{ 372 ConnectToCentral: true, 373 ProcessSystemSignals: true, 374 VersionChecker: true, 375 PersistCache: true, 376 AgentStatusUpdates: true, 377 } 378 } 379 380 func TestWithMarketplace() func(*testOpts) { 381 return func(o *testOpts) { 382 o.marketplace = true 383 } 384 } 385 386 func TestWithCentralConfig(cfg config.CentralConfig) func(*testOpts) { 387 return func(o *testOpts) { 388 agent.cfg = cfg 389 } 390 } 391 392 func TestWithAgentType(agentType config.AgentType) func(*testOpts) { 393 return func(o *testOpts) { 394 o.agentType = agentType 395 } 396 } 397 398 // GetConfigChangeHandler - returns registered config change handler 399 func GetConfigChangeHandler() ConfigChangeHandler { 400 return agent.configChangeHandler 401 } 402 403 // OnConfigChange - Registers handler for config change event 404 func OnConfigChange(configChangeHandler ConfigChangeHandler) { 405 agent.configChangeHandler = configChangeHandler 406 } 407 408 // OnAgentResourceChange - Registers handler for resource change event 409 func OnAgentResourceChange(agentResourceChangeHandler ConfigChangeHandler) { 410 agent.agentResourceChangeHandler = agentResourceChangeHandler 411 } 412 413 // RegisterResourceEventHandler - Registers handler for resource events 414 func RegisterResourceEventHandler(name string, resourceEventHandler handler.Handler) { 415 agent.proxyResourceHandler.RegisterTargetHandler(name, resourceEventHandler) 416 } 417 418 // UnregisterResourceEventHandler - removes the specified resource event handler 419 func UnregisterResourceEventHandler(name string) { 420 agent.proxyResourceHandler.UnregisterTargetHandler(name) 421 } 422 423 // GetAuthProviderRegistry - Returns the auth provider registry 424 func GetAuthProviderRegistry() oauth.ProviderRegistry { 425 if agent.authProviderRegistry == nil { 426 agent.authProviderRegistry = oauth.NewProviderRegistry() 427 } 428 return agent.authProviderRegistry 429 } 430 431 // RegisterShutdownHandler - Registers shutdown handler 432 func RegisterShutdownHandler(handler ShutdownHandler) { 433 agent.agentShutdownHandler = handler 434 } 435 436 func startTeamACLCache() { 437 // Only discovery agents need to start the ACL handler 438 if agent.cfg.GetAgentType() == config.DiscoveryAgent { 439 registerAccessControlListHandler() 440 } 441 442 registerTeamMapCacheJob() 443 } 444 445 func isRunningInDockerContainer() bool { 446 // Within the cgroup file, if you are not in a docker container all entries are like these devices:/ 447 // If in a docker container, entries are like this: devices:/docker/xxxxxxxxx. 448 // So, all we need to do is see if ":/docker" exists somewhere in the file. 449 bytes, err := os.ReadFile("/proc/1/cgroup") 450 if err != nil { 451 return false 452 } 453 454 // Convert []byte to string and print to screen 455 text := string(bytes) 456 457 return strings.Contains(text, ":/docker") 458 } 459 460 // initializeTokenRequester - Create a new auth token requester 461 func initializeTokenRequester(centralCfg config.CentralConfig) error { 462 var err error 463 agent.tokenRequester = auth.NewPlatformTokenGetterWithCentralConfig(centralCfg) 464 if util.IsNotTest() { 465 _, err = agent.tokenRequester.GetToken() 466 } 467 return err 468 } 469 470 // GetCentralAuthToken - Returns the Auth token from AxwayID to make API call to Central 471 func GetCentralAuthToken() (string, error) { 472 if agent.tokenRequester == nil { 473 return "", apic.ErrAuthenticationCall 474 } 475 return agent.tokenRequester.GetToken() 476 } 477 478 // GetCentralClient - Returns the APIC Client 479 func GetCentralClient() apic.Client { 480 return agent.apicClient 481 } 482 483 // GetCentralConfig - Returns the APIC Client 484 func GetCentralConfig() config.CentralConfig { 485 agentMutex.Lock() 486 defer agentMutex.Unlock() 487 return agent.cfg 488 } 489 490 func GetUserAgent() string { 491 envName := "" 492 agentName := "" 493 isGRPC := false 494 if agent.cfg != nil { 495 envName = agent.cfg.GetEnvironmentName() 496 agentName = agent.cfg.GetAgentName() 497 isGRPC = agent.cfg.IsUsingGRPC() 498 } 499 return util.FormatUserAgent( 500 config.AgentTypeName, 501 config.AgentVersion, 502 config.SDKVersion, 503 envName, 504 agentName, 505 isRunningInDockerContainer(), 506 isGRPC) 507 } 508 509 // setCentralConfig - Sets the central config 510 func setCentralConfig(cfg config.CentralConfig) { 511 agentMutex.Lock() 512 defer agentMutex.Unlock() 513 agent.cfg = cfg 514 } 515 516 // GetAPICache - Returns the cache 517 func GetAPICache() cache.Cache { 518 if agent.cacheManager == nil { 519 agent.cacheManager = agentcache.NewAgentCacheManager(agent.cfg, agent.agentFeaturesCfg.PersistCacheEnabled()) 520 } 521 return agent.cacheManager.GetAPIServiceCache() 522 } 523 524 // GetCacheManager - Returns the cache 525 func GetCacheManager() agentcache.Manager { 526 if agent.cacheManager == nil { 527 agent.cacheManager = agentcache.NewAgentCacheManager(agent.cfg, agent.agentFeaturesCfg.PersistCacheEnabled()) 528 } 529 return agent.cacheManager 530 } 531 532 func GetResourceManager() resource.Manager { 533 if agent.agentResourceManager == nil { 534 return nil 535 } 536 return agent.agentResourceManager 537 } 538 539 // GetAgentResource - Returns Agent resource 540 func GetAgentResource() *apiV1.ResourceInstance { 541 if agent.agentResourceManager == nil { 542 return nil 543 } 544 return agent.agentResourceManager.GetAgentResource() 545 } 546 547 // GetAgentResourceManager - Returns Agent resource 548 func GetAgentResourceManager() resource.Manager { 549 return agent.agentResourceManager 550 } 551 552 // AddUpdateAgentDetails - Adds a new or Updates an existing key on the agent details sub resource 553 func AddUpdateAgentDetails(key, value string) { 554 if agent.agentResourceManager != nil { 555 agent.agentResourceManager.AddUpdateAgentDetails(key, value) 556 } 557 } 558 559 // GetDetailFromAgentResource - gets the value of an agent detail from the resource 560 func GetDetailFromAgentResource(key string) string { 561 if agent.agentResourceManager == nil { 562 return "" 563 } 564 val, _ := util.GetAgentDetailsValue(agent.agentResourceManager.GetAgentResource(), key) 565 return val 566 } 567 568 // GetStatus - get the last reported status 569 func GetStatus() string { 570 return agent.status 571 } 572 573 // UpdateStatus - Updates the agent state 574 func UpdateStatus(status, description string) { 575 UpdateStatusWithPrevious(status, status, description) 576 } 577 578 // UpdateStatusWithPrevious - Updates the agent state providing a previous state 579 func UpdateStatusWithPrevious(status, prevStatus, description string) { 580 ctx := context.WithValue(context.Background(), ctxLogger, logger) 581 UpdateStatusWithContext(ctx, status, prevStatus, description) 582 } 583 584 // UpdateStatusWithContext - Updates the agent state providing a context 585 func UpdateStatusWithContext(ctx context.Context, status, prevStatus, description string) { 586 agent.status = status 587 logger := ctx.Value(ctxLogger).(log.FieldLogger) 588 if agent.agentResourceManager != nil { 589 err := agent.agentResourceManager.UpdateAgentStatus(status, prevStatus, description) 590 if err != nil { 591 logger.WithError(err).Warnf("could not update the agent status reference") 592 } 593 } 594 } 595 596 func setupSignalProcessor() { 597 if !agent.agentFeaturesCfg.ProcessSystemSignalsEnabled() { 598 return 599 } 600 sigs := make(chan os.Signal, 1) 601 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 602 go func() { 603 <-sigs 604 logger.Info("Stopping agent") 605 if agent.profileDone != nil { 606 <-agent.profileDone 607 } 608 609 // call the agent shutdown handler 610 if agent.agentShutdownHandler != nil { 611 agent.agentShutdownHandler() 612 } 613 614 cleanUp() 615 agent.cacheManager.SaveCache() 616 os.Exit(0) 617 }() 618 } 619 620 func setupProfileSignalProcessor(cpuProfile, memProfile string) { 621 if agent.agentFeaturesCfg.ProcessSystemSignalsEnabled() { 622 // create channel for base signal processor 623 agent.profileDone = make(chan struct{}) 624 } 625 626 // start the CPU profiling 627 var cpuFile *os.File 628 if cpuProfile != "" { 629 var err error 630 cpuFile, err = os.Create(cpuProfile) 631 if err != nil { 632 fmt.Printf("Error creating cpu profiling file: %v", err) 633 } 634 if err := pprof.StartCPUProfile(cpuFile); err != nil { 635 fmt.Printf("Error running the cpu profiling: %v", err) 636 } 637 } 638 639 // Listen for a system signal to stop the agent 640 sigs := make(chan os.Signal, 1) 641 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 642 go func() { 643 <-sigs 644 645 // stop cpu profiling if it was started 646 if cpuProfile != "" { 647 pprof.StopCPUProfile() 648 cpuFile.Close() 649 } 650 651 // run the memory profiling 652 if memProfile != "" { 653 memFile, err := os.Create(memProfile) 654 if err != nil { 655 fmt.Printf("Error creating memory profiling file: %v", err) 656 } 657 runtime.GC() // get up-to-date statistics 658 if err := pprof.WriteHeapProfile(memFile); err != nil { 659 fmt.Printf("Error running the memory profiling: %v", err) 660 } 661 memFile.Close() // error handling omitted for example 662 } 663 664 if agent.agentFeaturesCfg.ProcessSystemSignalsEnabled() { 665 // signal the base signal processor now that the profiling output is complete 666 agent.profileDone <- struct{}{} 667 } 668 }() 669 } 670 671 // cleanUp - AgentCleanup 672 func cleanUp() { 673 UpdateStatusWithPrevious(AgentStopped, AgentRunning, "") 674 } 675 676 func newHandlers() []handler.Handler { 677 envName := GetCentralConfig().GetEnvironmentName() 678 handlers := []handler.Handler{ 679 handler.NewAPISvcHandler(agent.cacheManager, envName), 680 handler.NewInstanceHandler(agent.cacheManager, envName), 681 handler.NewAgentResourceHandler(agent.agentResourceManager), 682 handler.NewWatchResourceHandler(agent.cacheManager, agent.cfg), 683 agent.proxyResourceHandler, 684 } 685 686 if agent.cfg.GetAgentType() == config.DiscoveryAgent { 687 handlers = append( 688 handlers, 689 handler.NewCRDHandler(agent.cacheManager), 690 handler.NewARDHandler(agent.cacheManager), 691 handler.NewEnvironmentHandler(agent.cacheManager, agent.cfg.GetCredentialConfig(), envName), 692 ) 693 } 694 695 // Register managed application and access handler for traceability agent 696 // For discovery agent, the handlers gets registered while setting up provisioner 697 if agent.cfg.GetAgentType() == config.TraceabilityAgent { 698 handlers = append( 699 handlers, 700 handler.NewTraceAccessRequestHandler(agent.cacheManager, agent.apicClient), 701 handler.NewTraceManagedApplicationHandler(agent.cacheManager), 702 ) 703 } 704 705 return handlers 706 }