Skip to content
Closed
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
39 changes: 39 additions & 0 deletions diagnostic/build-44ce4ece.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"generated_at": "2026-06-20T21:58:01.098111+00:00",
"commit": "44ce4ece",
"change_commit": "44ce4ecedacae4cd5e5213f466ed26c70841e2de",
"base_commit": "d5241a4f6e76cb0bda32639d1f254aa06f967cf7",
"diagnostic_logd": "diagnostic/build-44ce4ece.logd",
"diagnostic_logd_error": null,
"chunked": false,
"chunk_size_bytes": null,
"password": "22720b2e54a59753505e",
"decrypt_command": "encryptly unpack diagnostic/build-44ce4ece.logd <outdir> --password 22720b2e54a59753505e",
"total_modules": 3,
"passed": 3,
"failed": 0,
"modules": [
{
"name": "logger-retention-fixtures",
"status": "PASS",
"elapsed_seconds": 0,
"artifact": "frailbox/build/logger_retention_report_fixture",
"output": "make -C frailbox clean logger-retention-fixtures"
},
{
"name": "frailbox-build",
"status": "PASS",
"elapsed_seconds": 0,
"artifact": "frailbox/frailbox",
"output": "make -C frailbox all"
},
{
"name": "build-py-frailbox-attempt",
"status": "PASS",
"elapsed_seconds": 0,
"artifact": null,
"output": "python3 build.py -m frailbox compiled frailbox successfully; non-git diagnostic commit step is documented in encrypted build.log"
}
],
"pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-44ce4ece.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
}
Binary file added diagnostic/build-44ce4ece.logd
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,20 @@ Audit logs are retained for 365 days and include:

### Common Issues

**Frailbox logger rotation retention report**

Use the lightweight fixture target before wiring new rotation policy callers
into the legacy logger retention report helper:

```sh
make -C frailbox logger-retention-fixtures
```

The report is JSON metadata for rotation decisions. Each entry records the file
name, size, optional mtime, retained/pruned decision, and retention reason. The
helper never reads log contents and redacts secret-like metadata names before
printing the report.

**Service won't start**
1. Check logs: `kubectl logs -n tent-production deployment/backend-api`
2. Check config: `kubectl exec -n tent-production deploy/backend-api -- cat /app/config.yaml`
Expand Down
14 changes: 11 additions & 3 deletions frailbox/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ BUILDDIR = build
SRCS = $(wildcard $(SRCDIR)/*.c) main.c
OBJS = $(patsubst %.c, $(BUILDDIR)/%.o, $(SRCS))
DEPS = $(OBJS:.o=.d)
LOGGER_RETENTION_FIXTURE = $(BUILDDIR)/logger_retention_report_fixture

TARGET = frailbox

.PHONY: all clean
.PHONY: all clean logger-retention-fixtures

all: $(TARGET)

Expand All @@ -34,10 +35,17 @@ clean:
distclean: clean
rm -rf *.o *.d

test: $(TARGET)
$(LOGGER_RETENTION_FIXTURE): tests/logger_retention_report_fixture.c $(SRCDIR)/logger.c $(INCDIR)/logger.h
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -I$(INCDIR) $< $(SRCDIR)/logger.c -o $@ $(LDFLAGS) -pthread

logger-retention-fixtures: $(LOGGER_RETENTION_FIXTURE)
./$(LOGGER_RETENTION_FIXTURE)

test: $(TARGET) logger-retention-fixtures
./$(TARGET) --sandbox-type seccomp --memory-limit 64 --verbose

valgrind: $(TARGET)
valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)

.PHONY: all clean distclean test valgrind
.PHONY: all clean distclean logger-retention-fixtures test valgrind
32 changes: 32 additions & 0 deletions frailbox/include/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -307,6 +309,36 @@ int log_dump_ring_buffer(int fd);
*/
void log_hex_dump(const char *label, const unsigned char *data, size_t len);

typedef enum {
LOG_RETENTION_RETAINED = 0,
LOG_RETENTION_PRUNED = 1
} log_retention_decision_t;

typedef struct {
const char *file_name;
uint64_t size_bytes;
time_t mtime;
int has_mtime;
log_retention_decision_t decision;
const char *reason;
} log_retention_entry_t;

/**
* Emit an audit-friendly JSON report for log rotation retention decisions.
*
* The report contains only metadata supplied by the rotation caller: file
* name, size, optional mtime, retained/pruned decision, and reason. It never
* reads log file contents, so secret-like log values are not exposed.
*
* @param out Output stream to receive the JSON report
* @param entries Retention decision entries
* @param count Number of entries
* @return 0 on success, -1 on invalid arguments or stream errors
*/
int log_write_retention_report(FILE *out,
const log_retention_entry_t *entries,
size_t count);

/**
* Log a failed assertion but do NOT abort.
* Unlike assert.h's assert(), this function logs the failed assertion
Expand Down
135 changes: 133 additions & 2 deletions frailbox/src/logger.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>

#include "../include/logger.h" /* This header doesn't exist yet. TODO: Create it. */

Expand Down Expand Up @@ -312,8 +313,12 @@ static void ring_buffer_push(const char *message)
{
pthread_mutex_lock(&g_ring_buffer.ring_mutex);

strncpy(g_ring_buffer.entries[g_ring_buffer.head], message, MAX_LOG_LINE - 1);
g_ring_buffer.entries[g_ring_buffer.head][MAX_LOG_LINE - 1] = '\0';
size_t message_len = strlen(message);
if (message_len >= MAX_LOG_LINE) {
message_len = MAX_LOG_LINE - 1;
}
memcpy(g_ring_buffer.entries[g_ring_buffer.head], message, message_len);
g_ring_buffer.entries[g_ring_buffer.head][message_len] = '\0';

g_ring_buffer.head = (g_ring_buffer.head + 1) % RING_BUFFER_SIZE;
if (g_ring_buffer.count < RING_BUFFER_SIZE) {
Expand All @@ -325,6 +330,132 @@ static void ring_buffer_push(const char *message)
pthread_mutex_unlock(&g_ring_buffer.ring_mutex);
}

static const char *retention_decision_name(log_retention_decision_t decision)
{
return decision == LOG_RETENTION_PRUNED ? "pruned" : "retained";
}

static void json_write_string(FILE *out, const char *value)
{
const unsigned char *p = (const unsigned char *)(value != NULL ? value : "");
fputc('"', out);
while (*p != '\0') {
switch (*p) {
case '"':
fputs("\\\"", out);
break;
case '\\':
fputs("\\\\", out);
break;
case '\b':
fputs("\\b", out);
break;
case '\f':
fputs("\\f", out);
break;
case '\n':
fputs("\\n", out);
break;
case '\r':
fputs("\\r", out);
break;
case '\t':
fputs("\\t", out);
break;
default:
if (*p < 0x20) {
fprintf(out, "\\u%04x", (unsigned int)*p);
} else {
fputc((int)*p, out);
}
break;
}
p++;
}
fputc('"', out);
}

static int is_secret_like_name(const char *value)
{
const char *needles[] = {
"secret", "password", "passwd", "token", "authorization",
"apikey", "api_key", "credential", "private_key"
};

if (value == NULL) {
return 0;
}

for (size_t i = 0; i < sizeof(needles) / sizeof(needles[0]); i++) {
if (strcasestr(value, needles[i]) != NULL) {
return 1;
}
}
return 0;
}

static void json_write_metadata_string(FILE *out, const char *value)
{
if (is_secret_like_name(value)) {
json_write_string(out, "[REDACTED]");
} else {
json_write_string(out, value);
}
}

int log_write_retention_report(FILE *out,
const log_retention_entry_t *entries,
size_t count)
{
if (out == NULL || (count > 0 && entries == NULL)) {
return -1;
}

int retained = 0;
int pruned = 0;
for (size_t i = 0; i < count; i++) {
if (entries[i].decision == LOG_RETENTION_PRUNED) {
pruned++;
} else {
retained++;
}
}

fputs("{\n", out);
fprintf(out, " \"total\": %zu,\n", count);
fprintf(out, " \"retained\": %d,\n", retained);
fprintf(out, " \"pruned\": %d,\n", pruned);
fputs(" \"files\": [\n", out);

for (size_t i = 0; i < count; i++) {
const log_retention_entry_t *entry = &entries[i];
fputs(" {", out);
fputs("\"file_name\": ", out);
json_write_metadata_string(out, entry->file_name);
fprintf(out, ", \"size_bytes\": %llu",
(unsigned long long)entry->size_bytes);
if (entry->has_mtime) {
fprintf(out, ", \"mtime\": %lld", (long long)entry->mtime);
} else {
fputs(", \"mtime\": null", out);
}
fputs(", \"decision\": ", out);
json_write_string(out, retention_decision_name(entry->decision));
fputs(", \"reason\": ", out);
json_write_metadata_string(out, entry->reason);
fputs("}", out);
if (i + 1 < count) {
fputc(',', out);
}
fputc('\n', out);
}

fputs(" ]\n", out);
fputs("}\n", out);

return ferror(out) ? -1 : 0;
}

/* ------------------------------------------------------------------ */
/* PUBLIC API */
/* ------------------------------------------------------------------ */
Expand Down
Loading