Skip to content
Merged
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
26 changes: 21 additions & 5 deletions client/orbit_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ type OrbitClient struct {

// >>> OPENFRAME(agent-openframe-mode): openframe-mode flag + bearer-token auth manager on the orbit client — openframe/docs/agent-openframe-mode.md
// openframe mode
openFrameMode bool
authManager *openframe.OpenFrameAuthorizationManager
openFrameMode bool
authManager *openframe.OpenFrameAuthorizationManager
machineIdProvider *openframe.OpenFrameMachineIdProvider
// <<< OPENFRAME(agent-openframe-mode)
}

Expand Down Expand Up @@ -177,14 +178,21 @@ func (oc *OrbitClient) requestWithExternal(verb string, pathOrURL string, params
request.Header.Set("Content-Type", "application/json")
}
// <<< OPENFRAME(agent-json-content-type)
// >>> OPENFRAME(agent-openframe-mode): inject Bearer auth header on every request when in openframe mode — openframe/docs/agent-openframe-mode.md
// >>> OPENFRAME(agent-openframe-mode): inject Bearer auth + x-machine-id headers on every request when in openframe mode — openframe/docs/agent-openframe-mode.md
if oc.openFrameMode {
authToken := oc.authManager.GetToken()
if authToken != "" {
request.Header.Add("Authorization", "Bearer "+authToken)
} else {
log.Debug().Msg("authToken is empty, not adding Authorization header")
}

if oc.machineIdProvider != nil {
machineId := oc.machineIdProvider.GetMachineId()
if machineId != "" {
request.Header.Add("x-machine-id", machineId)
}
}
}
// <<< OPENFRAME(agent-openframe-mode)
}
Expand Down Expand Up @@ -259,6 +267,13 @@ func NewOrbitClient(
nodeKeyFilePath := filepath.Join(rootDir, constant.OrbitNodeKeyFileName)
ctx, cancelFunc := context.WithCancel(context.Background())

// >>> OPENFRAME(agent-openframe-mode): machine-id provider for the x-machine-id header — openframe/docs/agent-openframe-mode.md
var machineIdProvider *openframe.OpenFrameMachineIdProvider
if openFrameMode {
machineIdProvider = openframe.NewOpenFrameMachineIdProvider()
}
// <<< OPENFRAME(agent-openframe-mode)

return &OrbitClient{
nodeKeyFilePath: nodeKeyFilePath,
BaseClient: bc,
Expand All @@ -272,8 +287,9 @@ func NewOrbitClient(
receiverUpdateCancelFunc: cancelFunc,
hostIdentityCertPath: hostIdentityCertPath,
// >>> OPENFRAME(agent-openframe-mode): wire openframe fields into the OrbitClient literal — openframe/docs/agent-openframe-mode.md
authManager: authManager,
openFrameMode: openFrameMode,
authManager: authManager,
openFrameMode: openFrameMode,
machineIdProvider: machineIdProvider,
// <<< OPENFRAME(agent-openframe-mode)
}, nil
}
Expand Down
5 changes: 5 additions & 0 deletions openframe/docs/agent-openframe-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ extra constructor arguments — `openFrameMode bool` and
`Authorization: Bearer <token>` to every request. An empty token logs a debug
line and sends no header (the gateway then rejects the request, and the next
refresh cycle will repopulate the token).
- **Machine-id header.** Also in `requestWithExternal`, `OpenFrameMachineIdProvider`
reads the shared OpenFrame `machine_id` file (written by openframe-client) and,
if non-empty, adds `x-machine-id: <id>` to every request for gateway/firewall
machine identification. A missing or empty file sends no header.

The node-key enrollment behavior is unchanged by OpenFrame mode and is documented
separately in [node-key-management.md](node-key-management.md).
Expand Down Expand Up @@ -194,6 +198,7 @@ host API (see [api-expose-osquery-host-id.md](api-expose-osquery-host-id.md)).
| `server/service/openframe/openframe-encryption-service.go` | AES-GCM decryption |
| `server/service/openframe/openframe-token-extractor.go` | Read + decrypt token file |
| `server/service/openframe/openframe_authorization_manager.go` | Thread-safe token holder |
| `server/service/openframe/openframe_machine_id_provider.go` | Cached reader of the shared OpenFrame `machine_id` file |
| `server/service/openframe/openframe_token_refresher.go` | 5-second cron token refresh |
| `server/service/base_client.go` | Additional enrollment/debug logging |

Expand Down
92 changes: 92 additions & 0 deletions server/service/openframe/openframe_machine_id_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package openframe

import (
"os"
"path/filepath"
"runtime"
"strings"
"sync"

"github.com/rs/zerolog/log"
)

// OpenFrameMachineIdProvider reads the shared machine ID written by openframe-client.
type OpenFrameMachineIdProvider struct {
machineId string
initialized bool
mu sync.RWMutex
}

func NewOpenFrameMachineIdProvider() *OpenFrameMachineIdProvider {
return &OpenFrameMachineIdProvider{}
}

// GetMachineId returns the machine ID, reading from file if not cached
func (p *OpenFrameMachineIdProvider) GetMachineId() string {
p.mu.RLock()
if p.initialized {
machineId := p.machineId
p.mu.RUnlock()
return machineId
}
p.mu.RUnlock()

p.mu.Lock()
defer p.mu.Unlock()

if p.initialized {
return p.machineId
}

p.machineId = p.readFromFile()
// Don't cache an empty read: the file may not be written yet, retry on the next call
p.initialized = p.machineId != ""
return p.machineId
}

// Refresh forces a re-read of machine ID from file
func (p *OpenFrameMachineIdProvider) Refresh() {
p.mu.Lock()
defer p.mu.Unlock()

p.machineId = p.readFromFile()
p.initialized = true
}

func (p *OpenFrameMachineIdProvider) getFilePath() string {
switch runtime.GOOS {
case "windows":
programData := os.Getenv("ProgramData")
if programData == "" {
return ""
}
return filepath.Join(programData, "OpenFrame", "machine_id")
case "darwin":
return "/Library/Application Support/OpenFrame/machine_id"
default:
return "/var/lib/openframe/machine_id"
}
}

func (p *OpenFrameMachineIdProvider) readFromFile() string {
path := p.getFilePath()
if path == "" {
log.Warn().Msg("Could not determine machine_id file path")
return ""
}

data, err := os.ReadFile(path)
if err != nil {
log.Debug().Str("path", path).Err(err).Msg("Could not read machine_id file")
return ""
}

machineId := strings.TrimSpace(string(data))
if machineId == "" {
log.Warn().Str("path", path).Msg("Machine ID file is empty")
return ""
}

log.Debug().Str("path", path).Msg("Read machine ID from file")
return machineId
}
Loading