un-inception/clients/c/examples/credentials.c
russell@unturf.com 1e01d09883 feat: per-client Makefile infrastructure for 4-mode testing
Add per-client Makefiles for C, Python, and Go with:
- CLI mode: Tests --help, arg parsing, syntax validation
- Library mode: Unit tests, import verification
- Integration mode: API contract validation (with credentials)
- Functional mode: Real-world scenario tests

C client:
- 22 library tests (SHA-256, HMAC-SHA256, detect_language)
- Full unsandbox.c implementation with examples

Python client (sync + async):
- Delegates to sync/ and async/ subdirectories
- pytest-based test suites with coverage
- Examples for concurrent execution, streaming

Go client:
- Delegates to sync/ and async/ subdirectories
- go test integration with vet and fmt

Also update detect-changes.sh to detect changes in
both root-level un.* files AND clients/ directory.
2026-01-15 16:39:56 -05:00

392 lines
14 KiB
C

/*
* credentials.c - Credential loading from 4 sources
*
* Demonstrates how the un.c library loads credentials from multiple sources
* with priority ordering. This example shows:
* - Priority ordering (highest to lowest)
* - Environment variables
* - CLI flags
* - Config file (~/.unsandbox/accounts.csv)
* - Account selection
*
* Compile:
* gcc -o credentials credentials.c -I../../.. -lcurl -lwebsockets -lssl -lcrypto
*
* Run:
* ./credentials
*
* Or with specific credentials:
* ./credentials --public unsb-pk-xxxxx --secret unsb-sk-xxxxx
*
* Expected output:
* Demonstrates credential loading from all 4 sources
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
/* ============================================================================
* Helper functions
* ============================================================================ */
/* Print a value, masking sensitive info after first 8 characters */
static void print_masked(const char *label, const char *value) {
if (!value || strlen(value) == 0) {
printf("%s: (not set)\n", label);
return;
}
size_t len = strlen(value);
printf("%s: ", label);
if (len <= 8) {
printf("****\n");
} else {
printf("%.8s...****\n", value);
}
}
/* Check if a file exists */
static int file_exists(const char *path) {
struct stat sb;
return (stat(path, &sb) == 0 && S_ISREG(sb.st_mode));
}
/* Get home directory */
static const char* get_home_dir(void) {
const char *home = getenv("HOME");
if (home) return home;
struct passwd *pw = getpwuid(getuid());
if (pw) return pw->pw_dir;
return ".";
}
/* ============================================================================
* Credential priority demonstration
* ============================================================================ */
void explain_priority(void) {
printf("Credential Priority (Highest to Lowest)\n");
printf("=======================================\n\n");
printf("Priority 1: CLI Flags (HIGHEST)\n");
printf(" un -p PUBLIC_KEY -k SECRET_KEY\n");
printf(" These always take precedence if provided.\n\n");
printf("Priority 2: Environment Variables\n");
printf(" UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY\n");
printf(" Used if CLI flags not provided.\n\n");
printf("Priority 3: Config File\n");
printf(" ~/.unsandbox/accounts.csv\n");
printf(" Format: public_key,secret_key (one per line)\n");
printf(" Account selection: --account N (0-based, default: 0)\n");
printf(" Or: UNSANDBOX_ACCOUNT environment variable\n\n");
printf("Priority 4: Default (LOWEST)\n");
printf(" If none of the above are set, execution fails.\n");
printf(" \"Error: API credentials required\"\n\n");
}
/* ============================================================================
* Source 1: CLI Flags
* ============================================================================ */
void explain_cli_flags(void) {
printf("\nSource 1: CLI Flags\n");
printf("===================\n");
printf("Command line format:\n");
printf(" un -p unsb-pk-xxxxx -k unsb-sk-xxxxx\n");
printf(" un --public unsb-pk-xxxxx --secret unsb-sk-xxxxx\n\n");
printf("Example usage in code:\n");
printf(" int main(int argc, char *argv[]) {\n");
printf(" const char *public_key = NULL;\n");
printf(" const char *secret_key = NULL;\n");
printf("\n");
printf(" // Parse command line\n");
printf(" for (int i = 1; i < argc; i++) {\n");
printf(" if ((strcmp(argv[i], \"-p\") == 0 ||\n");
printf(" strcmp(argv[i], \"--public\") == 0) &&\n");
printf(" i + 1 < argc) {\n");
printf(" public_key = argv[++i];\n");
printf(" }\n");
printf(" if ((strcmp(argv[i], \"-k\") == 0 ||\n");
printf(" strcmp(argv[i], \"--secret\") == 0) &&\n");
printf(" i + 1 < argc) {\n");
printf(" secret_key = argv[++i];\n");
printf(" }\n");
printf(" }\n");
printf("\n");
printf(" // Check if both provided\n");
printf(" if (!public_key || !secret_key) {\n");
printf(" // Fall back to next priority\n");
printf(" }\n");
printf(" }\n\n");
printf("Advantages:\n");
printf(" - Highest priority (can't be accidentally overridden)\n");
printf(" - Unique credentials per invocation\n");
printf(" - Good for automation scripts\n\n");
printf("Disadvantages:\n");
printf(" - Credentials visible in process list (if not careful)\n");
printf(" - Must pass on every invocation\n");
}
/* ============================================================================
* Source 2: Environment Variables
* ============================================================================ */
void explain_env_vars(void) {
printf("\nSource 2: Environment Variables\n");
printf("================================\n");
const char *pk = getenv("UNSANDBOX_PUBLIC_KEY");
const char *sk = getenv("UNSANDBOX_SECRET_KEY");
const char *account = getenv("UNSANDBOX_ACCOUNT");
printf("Environment variables:\n");
printf(" UNSANDBOX_PUBLIC_KEY ");
print_masked("", pk);
printf(" UNSANDBOX_SECRET_KEY ");
print_masked("", sk);
printf(" UNSANDBOX_ACCOUNT %s (default: 0)\n\n", account ? account : "(not set)");
printf("How to set:\n");
printf(" export UNSANDBOX_PUBLIC_KEY=unsb-pk-xxxxx\n");
printf(" export UNSANDBOX_SECRET_KEY=unsb-sk-xxxxx\n");
printf(" export UNSANDBOX_ACCOUNT=0 # optional\n\n");
printf("Or in one line:\n");
printf(" UNSANDBOX_PUBLIC_KEY=xxx UNSANDBOX_SECRET_KEY=yyy ./un script.py\n\n");
printf("How to read in C:\n");
printf(" const char *pk = getenv(\"UNSANDBOX_PUBLIC_KEY\");\n");
printf(" const char *sk = getenv(\"UNSANDBOX_SECRET_KEY\");\n");
printf(" const char *account_str = getenv(\"UNSANDBOX_ACCOUNT\");\n");
printf(" int account_idx = account_str ? atoi(account_str) : 0;\n\n");
printf("Advantages:\n");
printf(" - Don't appear in process list\n");
printf(" - Can persist in shell session\n");
printf(" - Standard Unix convention\n\n");
printf("Disadvantages:\n");
printf(" - Can be inherited by child processes\n");
printf(" - Visible to debuggers\n");
}
/* ============================================================================
* Source 3: Config File
* ============================================================================ */
void explain_config_file(void) {
printf("\nSource 3: Config File (~/.unsandbox/accounts.csv)\n");
printf("==================================================\n");
const char *home = get_home_dir();
char config_path[512];
snprintf(config_path, sizeof(config_path), "%s/.unsandbox/accounts.csv", home);
printf("Config file location:\n");
printf(" %s\n\n", config_path);
printf("File format (one account per line):\n");
printf(" unsb-pk-account1,unsb-sk-account1\n");
printf(" unsb-pk-account2,unsb-sk-account2\n");
printf(" unsb-pk-account3,unsb-sk-account3\n\n");
printf("How to create:\n");
printf(" mkdir -p ~/.unsandbox\n");
printf(" cat > ~/.unsandbox/accounts.csv << 'EOF'\n");
printf(" unsb-pk-xxxxx,unsb-sk-xxxxx\n");
printf(" unsb-pk-yyyyy,unsb-sk-yyyyy\n");
printf(" EOF\n");
printf(" chmod 600 ~/.unsandbox/accounts.csv\n\n");
printf("How to select account:\n");
printf(" un --account 0 script.py # First account (default)\n");
printf(" un --account 1 script.py # Second account\n");
printf(" UNSANDBOX_ACCOUNT=2 un script.py # Environment variable\n\n");
printf("Current config file status:\n");
if (file_exists(config_path)) {
printf(" [EXISTS] %s\n", config_path);
printf(" Note: Contents not shown for security\n");
} else {
printf(" [MISSING] %s\n", config_path);
printf(" To create: mkdir -p ~/.unsandbox\n");
printf(" echo 'pk,sk' > %s\n", config_path);
printf(" chmod 600 %s\n", config_path);
}
printf("\nHow to read in C:\n");
printf(" FILE *f = fopen(config_path, \"r\");\n");
printf(" if (f) {\n");
printf(" char line[256];\n");
printf(" int account_num = 0;\n");
printf(" while (fgets(line, sizeof(line), f)) {\n");
printf(" if (account_num == account_idx) {\n");
printf(" char *pk = strtok(line, \",\");\n");
printf(" char *sk = strtok(NULL, \"\\\\n\");\n");
printf(" // Use pk and sk\n");
printf(" break;\n");
printf(" }\n");
printf(" account_num++;\n");
printf(" }\n");
printf(" fclose(f);\n");
printf(" }\n\n");
printf("Advantages:\n");
printf(" - Store multiple accounts\n");
printf(" - Secrets not in shell history\n");
printf(" - Can be version-controlled (separately)\n");
printf(" - Standard configuration file approach\n\n");
printf("Disadvantages:\n");
printf(" - File must exist at specific location\n");
printf(" - Must remember account index\n");
printf(" - Requires explicit setup\n");
printf(" - File permissions critical (should be 600)\n");
}
/* ============================================================================
* Source 4: Fallback/Error
* ============================================================================ */
void explain_fallback(void) {
printf("\nSource 4: Fallback (No Credentials)\n");
printf("====================================\n");
printf("If no credentials are found from sources 1-3:\n");
printf(" Error: API credentials required.\n");
printf(" Set UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars, or\n");
printf(" Use -p PUBLIC_KEY -k SECRET_KEY flags, or\n");
printf(" Create ~/.unsandbox/accounts.csv with: public_key,secret_key\n\n");
printf("Exit code: 1 (failure)\n\n");
}
/* ============================================================================
* Practical examples
* ============================================================================ */
void show_practical_examples(void) {
printf("\nPractical Examples\n");
printf("==================\n\n");
printf("Example 1: One-off execution\n");
printf(" UNSANDBOX_PUBLIC_KEY=unsb-pk-xxx UNSANDBOX_SECRET_KEY=unsb-sk-xxx \\\n");
printf(" ./un script.py\n\n");
printf("Example 2: Development session\n");
printf(" export UNSANDBOX_PUBLIC_KEY=unsb-pk-xxx\n");
printf(" export UNSANDBOX_SECRET_KEY=unsb-sk-xxx\n");
printf(" ./un script1.py\n");
printf(" ./un script2.py\n");
printf(" ./un script3.py\n\n");
printf("Example 3: Multiple accounts\n");
printf(" mkdir -p ~/.unsandbox\n");
printf(" echo 'unsb-pk-account1,unsb-sk-account1' >> ~/.unsandbox/accounts.csv\n");
printf(" echo 'unsb-pk-account2,unsb-sk-account2' >> ~/.unsandbox/accounts.csv\n");
printf(" ./un --account 0 script.py # Uses first account\n");
printf(" ./un --account 1 script.py # Uses second account\n\n");
printf("Example 4: Override with CLI flags\n");
printf(" export UNSANDBOX_PUBLIC_KEY=unsb-pk-env\n");
printf(" export UNSANDBOX_SECRET_KEY=unsb-sk-env\n");
printf(" ./un -p unsb-pk-cli -k unsb-sk-cli script.py # Uses CLI keys\n\n");
printf("Example 5: Automation in scripts\n");
printf(" #!/bin/bash\n");
printf(" export UNSANDBOX_PUBLIC_KEY=\"$(aws secretsmanager ...)\"\n");
printf(" export UNSANDBOX_SECRET_KEY=\"$(aws secretsmanager ...)\"\n");
printf(" ./un process_data.py\n\n");
}
/* ============================================================================
* Security best practices
* ============================================================================ */
void show_security_practices(void) {
printf("\nSecurity Best Practices\n");
printf("=======================\n\n");
printf("DO:\n");
printf(" ✓ Use environment variables for credentials\n");
printf(" ✓ Set config file permissions to 600 (chmod 600)\n");
printf(" ✓ Store secrets in secure vaults (AWS Secrets Manager, etc.)\n");
printf(" ✓ Rotate API keys regularly\n");
printf(" ✓ Use separate keys for different environments (dev/prod)\n");
printf(" ✓ Delete credentials from shell history: history -c\n");
printf(" ✓ Audit who has access to credentials\n\n");
printf("DON'T:\n");
printf(" ✗ Hardcode credentials in source code\n");
printf(" ✗ Commit credentials to version control\n");
printf(" ✗ Log credentials to console/files\n");
printf(" ✗ Pass credentials on command line in production\n");
printf(" ✗ Store credentials in plain text files (except ~/.unsandbox/accounts.csv)\n");
printf(" ✗ Share credentials between team members\n");
printf(" ✗ Use same credentials for dev and production\n\n");
}
/* ============================================================================
* Main demonstration
* ============================================================================ */
int main(int argc, char *argv[]) {
printf("Unsandbox Credential Management\n");
printf("================================\n\n");
/* Show priority */
explain_priority();
/* Show each source */
explain_cli_flags();
explain_env_vars();
explain_config_file();
explain_fallback();
/* Show practical examples */
show_practical_examples();
/* Show security practices */
show_security_practices();
printf("Current System State\n");
printf("====================\n\n");
printf("Environment variables:\n");
printf(" UNSANDBOX_PUBLIC_KEY: %s\n",
getenv("UNSANDBOX_PUBLIC_KEY") ? "(set)" : "(not set)");
printf(" UNSANDBOX_SECRET_KEY: %s\n",
getenv("UNSANDBOX_SECRET_KEY") ? "(set)" : "(not set)");
printf(" UNSANDBOX_ACCOUNT: %s\n",
getenv("UNSANDBOX_ACCOUNT") ? getenv("UNSANDBOX_ACCOUNT") : "(not set)");
char config_path[512];
snprintf(config_path, sizeof(config_path), "%s/.unsandbox/accounts.csv",
get_home_dir());
printf("\nConfig file:\n");
printf(" %s: %s\n", config_path,
file_exists(config_path) ? "exists" : "does not exist");
printf("\nTo use unsandbox, set credentials via one of these methods:\n");
printf(" 1. export UNSANDBOX_PUBLIC_KEY=... UNSANDBOX_SECRET_KEY=...\n");
printf(" 2. Create ~/.unsandbox/accounts.csv\n");
printf(" 3. Pass -p and -k flags to the command\n");
return 0;
}