google_guest_agent/main.go (233 lines of code) (raw):
// Copyright 2017 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// GCEGuestAgent is the Google Compute Engine guest agent executable.
package main
import (
"context"
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/command"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events"
mdsEvent "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/metadata"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/osinfo"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/scheduler"
"github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/telemetry"
"github.com/GoogleCloudPlatform/guest-agent/metadata"
"github.com/GoogleCloudPlatform/guest-agent/utils"
"github.com/GoogleCloudPlatform/guest-logging-go/logger"
)
// Certificates wrapps a list of certificate authorities.
type Certificates struct {
Certs []TrustedCert `json:"trustedCertificateAuthorities"`
}
// TrustedCert defines the object containing a public key.
type TrustedCert struct {
PublicKey string `json:"publicKey"`
}
var (
programName = "GCEGuestAgent"
version string
oldMetadata, newMetadata *metadata.Descriptor
osInfo osinfo.OSInfo
mdsClient *metadata.Client
addressManager = &addressMgr{}
)
const (
regKeyBase = `SOFTWARE\Google\ComputeEngine`
)
type manager interface {
Diff(ctx context.Context) (bool, error)
Disabled(ctx context.Context) (bool, error)
Set(ctx context.Context) error
Timeout(ctx context.Context) (bool, error)
}
func logStatus(name string, disabled bool) {
var status string
switch disabled {
case false:
status = "enabled"
case true:
status = "disabled"
}
logger.Infof("GCE %s manager status: %s", name, status)
}
func closeFile(c io.Closer) {
err := c.Close()
if err != nil {
logger.Warningf("Error closing file: %v.", err)
}
}
func availableManagers() []manager {
managers := []manager{
addressManager,
}
if runtime.GOOS == "windows" {
return append(managers,
newWsfcManager(),
&winAccountsMgr{},
&diagnosticsMgr{},
)
}
return append(managers,
&clockskewMgr{},
&osloginMgr{},
&accountsMgr{},
)
}
func runManager(ctx context.Context, mgr manager) {
disabled, err := mgr.Disabled(ctx)
if err != nil {
logger.Errorf("Failed to run manager's Disabled() call: %+v", err)
return
}
if disabled {
logger.Debugf("manager %#v disabled, skipping", mgr)
return
}
timeout, err := mgr.Timeout(ctx)
if err != nil {
logger.Errorf("[%#v] Failed to run manager Timeout() call: %+v", mgr, err)
return
}
diff, err := mgr.Diff(ctx)
if err != nil {
logger.Errorf("[%#v] Failed to run manager Diff() call: %+v", mgr, err)
return
}
if !timeout && !diff {
logger.Debugf("[%#v] Manager reports no diff", mgr)
return
}
logger.Debugf("running %#v manager", mgr)
if err := mgr.Set(ctx); err != nil {
logger.Errorf("[%#v] Failed to run manager Set() call: %s", mgr, err)
}
}
func runUpdate(ctx context.Context) {
var wg sync.WaitGroup
for _, mgr := range availableManagers() {
wg.Add(1)
go func(mgr manager) {
defer wg.Done()
runManager(ctx, mgr)
}(mgr)
}
wg.Wait()
}
func runAgent(ctx context.Context) {
opts := logger.LogOpts{LoggerName: programName}
if !cfg.Get().Core.CloudLoggingEnabled {
opts.DisableCloudLogging = true
}
if runtime.GOOS == "windows" {
opts.FormatFunction = logFormatWindows
opts.Writers = []io.Writer{&utils.SerialPort{Port: "COM1"}}
} else {
opts.FormatFunction = logFormat
opts.Writers = []io.Writer{os.Stdout}
// Local logging is syslog; we will just use stdout in Linux.
opts.DisableLocalLogging = true
}
if os.Getenv("GUEST_AGENT_DEBUG") != "" {
opts.Debug = true
}
if err := logger.Init(ctx, opts); err != nil {
fmt.Printf("Error initializing logger: %v", err)
os.Exit(1)
}
// Try flushing logs before exiting, if not flushed logs could go missing.
defer logger.Close()
logger.Infof("GCE Agent Started (version %s)", version)
osInfo = osinfo.Get()
mdsClient = metadata.New()
agentInit(ctx)
if cfg.Get().Unstable.CommandMonitorEnabled {
command.Init(ctx)
defer command.Close()
}
// Previous request to metadata *may* not have worked becasue routes don't get added until agentInit.
var err error
if newMetadata == nil {
// Error here doesn't matter, if we cant get metadata, we cant record telemetry.
newMetadata, err = mdsClient.Get(ctx)
if err != nil {
logger.Debugf("Error getting metdata: %v", err)
}
}
// Try to re-initialize logger now, we know after agentInit() is more likely to have metadata available.
// TODO: move all this metadata dependent code to its own metadata event handler.
if newMetadata != nil {
opts.ProjectName = newMetadata.Project.ProjectID
opts.MIG = newMetadata.Instance.Attributes.CreatedBy
if err := logger.Init(ctx, opts); err != nil {
logger.Errorf("Error initializing logger: %v", err)
}
}
// knownJobs is list of default jobs that run on a pre-defined schedule.
knownJobs := []scheduler.Job{telemetry.New(mdsClient, programName, version)}
scheduler.ScheduleJobs(ctx, knownJobs, false)
eventManager := events.Get()
if err := eventManager.AddDefaultWatchers(ctx); err != nil {
logger.Errorf("Error initializing event manager: %v", err)
return
}
if err := enableDisableOSLoginCertAuth(ctx); err != nil {
logger.Errorf("Failed to enable sshtrustedca watcher: %+v", err)
return
}
oldMetadata = &metadata.Descriptor{}
eventManager.Subscribe(mdsEvent.LongpollEvent, nil, func(ctx context.Context, evType string, data interface{}, evData *events.EventData) bool {
logger.Debugf("Handling metadata %q event.", evType)
// If metadata watcher failed there isn't much we can do, just ignore the event and
// allow the watcher to get it corrected.
if evData.Error != nil {
logger.Infof("Metadata event watcher failed, ignoring: %+v", evData.Error)
return true
}
if evData.Data == nil {
logger.Infof("Metadata event watcher didn't pass in the metadata, ignoring.")
return true
}
newMetadata = evData.Data.(*metadata.Descriptor)
if err := enableDisableOSLoginCertAuth(ctx); err != nil {
logger.Errorf("Failed to enable/disable sshtrustedca watcher: %+v", err)
}
runUpdate(ctx)
oldMetadata = newMetadata
return true
})
if err := eventManager.Run(ctx); err != nil {
logger.Fatalf("Failed to run event manager: %+v", err)
}
logger.Infof("GCE Agent Stopped")
}
func logFormatWindows(e logger.LogEntry) string {
now := time.Now().Format("2006/01/02 15:04:05")
// 2006/01/02 15:04:05 GCEGuestAgent This is a log message.
return fmt.Sprintf("%s %s: %s", now, programName, e.Message)
}
func logFormat(e logger.LogEntry) string {
switch e.Severity {
case logger.Error, logger.Critical, logger.Debug:
// ERROR file.go:82 This is a log message.
return fmt.Sprintf("%s %s:%d %s", strings.ToUpper(e.Severity.String()), e.Source.File, e.Source.Line, e.Message)
default:
// This is a log message.
return e.Message
}
}
func closer(c io.Closer) {
err := c.Close()
if err != nil {
logger.Warningf("Error closing %v: %v.", c, err)
}
}
func main() {
ctx := context.Background()
if err := cfg.Load(nil); err != nil {
fmt.Fprintf(os.Stderr, "Failed to load configuration: %+v", err)
os.Exit(1)
}
var action string
if len(os.Args) < 2 {
action = "run"
} else {
action = os.Args[1]
}
if action == "noservice" {
runAgent(ctx)
os.Exit(0)
}
if err := register(ctx, "GCEAgent", "GCEAgent", "", runAgent, action); err != nil {
logger.Fatalf("error registering service: %s", err)
}
}