update
This commit is contained in:
+7
-5
@@ -122,10 +122,12 @@ For later updates, run the repository update helper on one webserver at a time:
|
||||
/opt/idssys/ta-deployment-broker/update.sh
|
||||
```
|
||||
|
||||
The helper fast-forwards the current tracked branch, rebuilds the image, applies
|
||||
any pending migrations, recreates the broker container, and waits up to 150
|
||||
seconds for its Docker health check. It refuses to pull over tracked local
|
||||
changes or from a detached/untracked branch.
|
||||
The helper fetches and compares the current tracked branch with its upstream. If
|
||||
both commits match, it exits without touching Docker. When an update exists, it
|
||||
fast-forwards the branch, rebuilds the image, applies any pending migrations,
|
||||
recreates the broker container, and waits up to 150 seconds for its Docker
|
||||
health check. It refuses to pull over tracked local changes or from a detached,
|
||||
untracked, locally-ahead, or diverged branch.
|
||||
|
||||
## 7. HAProxy health check
|
||||
|
||||
@@ -178,7 +180,7 @@ technician's Gitea credentials for package operations.
|
||||
- Audit records are retained indefinitely unless an administrator establishes a
|
||||
database retention policy. The Codes view previews the newest 15 records; the
|
||||
Audit view can filter retained history by event, customer/deployment label,
|
||||
actor, host, package, source IP, or details and displays up to the newest 250
|
||||
user, host, package, source IP, or details and displays up to the newest 250
|
||||
matching events.
|
||||
- Keep `.env` outside Git and readable only by the service administrator.
|
||||
- Rotate both Gitea package tokens and the OAuth secret if either webserver is
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestAuditFiltersFromRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := httptest.NewRequest(
|
||||
"GET",
|
||||
"/portal?audit_range=7d&audit_event=package_downloaded&audit_customer=Acme&audit_actor=taiadmin&audit_hostname=pve01&audit_package=sentinelone-linux&audit_ip=10.10.1.25&audit_details=install-rmm",
|
||||
"/portal?audit_range=7d&audit_event=package_downloaded&audit_customer=Acme&audit_user=taiadmin&audit_hostname=pve01&audit_package=sentinelone-linux&audit_ip=10.10.1.25&audit_details=install-rmm",
|
||||
nil,
|
||||
)
|
||||
filters := auditFiltersFromRequest(request)
|
||||
@@ -19,7 +19,7 @@ func TestAuditFiltersFromRequest(t *testing.T) {
|
||||
TimeRange: "7d",
|
||||
EventType: "package_downloaded",
|
||||
CustomerLabel: "Acme",
|
||||
Actor: "taiadmin",
|
||||
User: "taiadmin",
|
||||
Hostname: "pve01",
|
||||
PackageSlug: "sentinelone-linux",
|
||||
SourceIP: "10.10.1.25",
|
||||
@@ -38,6 +38,14 @@ func TestAuditFiltersDefaultToThirtyDays(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuditFiltersAcceptLegacyActorParameter(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := httptest.NewRequest("GET", "/portal?audit_actor=legacy-user", nil)
|
||||
if filters := auditFiltersFromRequest(request); filters.User != "legacy-user" {
|
||||
t.Fatalf("user = %q, want legacy-user", filters.User)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasAuditQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !hasAuditQuery(httptest.NewRequest("GET", "/portal?audit_hostname=pve01", nil)) {
|
||||
@@ -54,7 +62,7 @@ func TestAuditQueryUsesPlaceholders(t *testing.T) {
|
||||
TimeRange: "all",
|
||||
EventType: "package_downloaded",
|
||||
CustomerLabel: "Acme",
|
||||
Actor: "taiadmin",
|
||||
User: "taiadmin",
|
||||
Hostname: "pve01",
|
||||
PackageSlug: "sentinelone-linux",
|
||||
SourceIP: "10.10.1.25",
|
||||
@@ -62,7 +70,7 @@ func TestAuditQueryUsesPlaceholders(t *testing.T) {
|
||||
}
|
||||
query, arguments := auditQuery(filters, 250)
|
||||
if strings.Contains(query, filters.CustomerLabel) ||
|
||||
strings.Contains(query, filters.Actor) ||
|
||||
strings.Contains(query, filters.User) ||
|
||||
strings.Contains(query, filters.Hostname) {
|
||||
t.Fatal("filter values must not be interpolated into the SQL query")
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ func hasAuditQuery(r *http.Request) bool {
|
||||
"audit_range",
|
||||
"audit_event",
|
||||
"audit_customer",
|
||||
"audit_user",
|
||||
"audit_actor",
|
||||
"audit_hostname",
|
||||
"audit_package",
|
||||
@@ -155,11 +156,16 @@ func auditFiltersFromRequest(r *http.Request) auditFilters {
|
||||
default:
|
||||
timeRange = "30d"
|
||||
}
|
||||
userFilter := query.Get("audit_user")
|
||||
if strings.TrimSpace(userFilter) == "" {
|
||||
// Preserve audit links created before the portal used "user" terminology.
|
||||
userFilter = query.Get("audit_actor")
|
||||
}
|
||||
return auditFilters{
|
||||
TimeRange: timeRange,
|
||||
EventType: limitedFilter(query.Get("audit_event"), 100),
|
||||
CustomerLabel: limitedFilter(query.Get("audit_customer"), 255),
|
||||
Actor: limitedFilter(query.Get("audit_actor"), 255),
|
||||
User: limitedFilter(userFilter, 255),
|
||||
Hostname: limitedFilter(query.Get("audit_hostname"), 255),
|
||||
PackageSlug: limitedFilter(query.Get("audit_package"), 100),
|
||||
SourceIP: limitedFilter(query.Get("audit_ip"), 64),
|
||||
@@ -224,7 +230,7 @@ func auditQuery(filters auditFilters, limit int) (string, []any) {
|
||||
value string
|
||||
}{
|
||||
{"COALESCE(a.customer_label, '')", filters.CustomerLabel},
|
||||
{"ae.actor", filters.Actor},
|
||||
{"ae.actor", filters.User},
|
||||
{"ae.hostname", filters.Hostname},
|
||||
{"ae.package_slug", filters.PackageSlug},
|
||||
{"ae.source_ip", filters.SourceIP},
|
||||
@@ -260,7 +266,7 @@ func (s *Server) listAuditEvents(r *http.Request, filters auditFilters, limit in
|
||||
if err := rows.Scan(
|
||||
&record.EventType,
|
||||
&record.CustomerLabel,
|
||||
&record.Actor,
|
||||
&record.User,
|
||||
&record.Hostname,
|
||||
&record.PackageSlug,
|
||||
&record.SourceIP,
|
||||
@@ -606,7 +612,7 @@ func validSHA256(value string) bool {
|
||||
func (s *Server) audit(
|
||||
ctx context.Context,
|
||||
eventType string,
|
||||
actor string,
|
||||
user string,
|
||||
authorizationID *uint64,
|
||||
hostname string,
|
||||
packageSlug string,
|
||||
@@ -622,7 +628,7 @@ func (s *Server) audit(
|
||||
`INSERT INTO audit_events
|
||||
(event_type, actor, authorization_id, hostname, package_slug, source_ip, details)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
eventType, actor, authID, hostname, packageSlug, sourceIP, details,
|
||||
eventType, user, authID, hostname, packageSlug, sourceIP, details,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ type authorizationRecord struct {
|
||||
type auditRecord struct {
|
||||
EventType string
|
||||
CustomerLabel string
|
||||
Actor string
|
||||
User string
|
||||
Hostname string
|
||||
PackageSlug string
|
||||
SourceIP string
|
||||
@@ -83,7 +83,7 @@ type auditFilters struct {
|
||||
TimeRange string
|
||||
EventType string
|
||||
CustomerLabel string
|
||||
Actor string
|
||||
User string
|
||||
Hostname string
|
||||
PackageSlug string
|
||||
SourceIP string
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<label>Customer / deployment <input name="audit_customer" value="{{.AuditFilters.CustomerLabel}}" placeholder="Acme cluster refresh"></label>
|
||||
<label>Technician / actor <input name="audit_actor" value="{{.AuditFilters.Actor}}" placeholder="taiadmin"></label>
|
||||
<label>User <input name="audit_user" value="{{.AuditFilters.User}}" placeholder="taiadmin"></label>
|
||||
<label>Hostname <input name="audit_hostname" value="{{.AuditFilters.Hostname}}" placeholder="pve01"></label>
|
||||
<label>Package ID <input name="audit_package" value="{{.AuditFilters.PackageSlug}}" placeholder="sentinelone-linux"></label>
|
||||
<label>Source IP <input name="audit_ip" value="{{.AuditFilters.SourceIP}}" placeholder="10.10.1.25"></label>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="audit-row audit-header" aria-hidden="true">
|
||||
<span>Event</span>
|
||||
<span>Customer / deployment</span>
|
||||
<span>Actor / host / package</span>
|
||||
<span>User / host / package</span>
|
||||
<span>Source / details</span>
|
||||
</div>
|
||||
{{range .AuditEvents}}
|
||||
@@ -41,7 +41,7 @@
|
||||
{{if .CustomerLabel}}<span>{{.CustomerLabel}}</span>{{else}}<span>—</span>{{end}}
|
||||
</div>
|
||||
<div>
|
||||
{{if .Actor}}<span>{{.Actor}}</span>{{end}}
|
||||
{{if .User}}<span>{{.User}}</span>{{end}}
|
||||
{{if .Hostname}}<span>{{.Hostname}}</span>{{end}}
|
||||
{{if .PackageSlug}}<span>{{.PackageSlug}}</span>{{end}}
|
||||
</div>
|
||||
|
||||
@@ -15,26 +15,44 @@ trap 'printf "ERROR: broker update failed at line %d.\n" "$LINENO" >&2' ERR
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
command -v git >/dev/null 2>&1 || fail "git is required"
|
||||
command -v docker >/dev/null 2>&1 || fail "docker is required"
|
||||
docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 is required"
|
||||
[[ -f compose.yaml ]] || fail "compose.yaml was not found in ${SCRIPT_DIR}"
|
||||
[[ -f .env ]] || fail ".env was not found in ${SCRIPT_DIR}"
|
||||
|
||||
if [[ "${1:-}" != "--post-pull" ]]; then
|
||||
branch="$(git symbolic-ref --quiet --short HEAD)" ||
|
||||
fail "the repository is in detached HEAD state"
|
||||
git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1 ||
|
||||
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null)" ||
|
||||
fail "branch ${branch} does not have an upstream branch"
|
||||
remote="$(git config --get "branch.${branch}.remote")" ||
|
||||
fail "branch ${branch} does not have a configured remote"
|
||||
git diff --quiet && git diff --cached --quiet ||
|
||||
fail "tracked local changes must be committed or stashed before updating"
|
||||
|
||||
printf 'Updating TAPM Deployment Broker from branch %s...\n' "$branch"
|
||||
git pull --ff-only
|
||||
printf 'Checking %s against %s...\n' "$branch" "$upstream"
|
||||
git fetch --prune "$remote"
|
||||
|
||||
local_commit="$(git rev-parse HEAD)"
|
||||
upstream_commit="$(git rev-parse '@{upstream}')"
|
||||
if [[ "$local_commit" == "$upstream_commit" ]]; then
|
||||
printf 'TAPM Deployment Broker is already up to date; no changes were made.\n'
|
||||
exit 0
|
||||
fi
|
||||
if git merge-base --is-ancestor "$local_commit" "$upstream_commit"; then
|
||||
printf 'Updating TAPM Deployment Broker from %s...\n' "$upstream"
|
||||
git merge --ff-only "$upstream_commit"
|
||||
elif git merge-base --is-ancestor "$upstream_commit" "$local_commit"; then
|
||||
fail "local branch ${branch} is ahead of ${upstream}; refusing to rebuild"
|
||||
else
|
||||
fail "local branch ${branch} has diverged from ${upstream}"
|
||||
fi
|
||||
|
||||
# Restart from the newly pulled script so updates to this file take effect.
|
||||
exec "$SCRIPT_PATH" --post-pull
|
||||
fi
|
||||
|
||||
command -v docker >/dev/null 2>&1 || fail "docker is required"
|
||||
docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 is required"
|
||||
[[ -f compose.yaml ]] || fail "compose.yaml was not found in ${SCRIPT_DIR}"
|
||||
[[ -f .env ]] || fail ".env was not found in ${SCRIPT_DIR}"
|
||||
|
||||
printf 'Building broker image...\n'
|
||||
docker compose build broker
|
||||
|
||||
|
||||
Reference in New Issue
Block a user