Skip to content
Open
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
70 changes: 70 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,76 @@ jobs:
path: |
~/rpmbuild/RPMS/${{ env.ARCH }}/${{ env.AIKIDO_ARTIFACT_RELEASE }}

build_lambda_layers:
name: Build Lambda layers php-${{ matrix.php_version }}
runs-on: ubuntu-24.04
needs: [ build_libs, build_php_extension_nts ]
strategy:
matrix:
php_version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
fail-fast: false

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Get Aikido version
run: |
AIKIDO_VERSION=$(grep '#define PHP_AIKIDO_VERSION' lib/php-extension/include/php_aikido.h | awk -F'"' '{print $2}')
echo "AIKIDO_VERSION=$AIKIDO_VERSION" >> $GITHUB_ENV
echo "AIKIDO_LIBZEN_VERSION=0.1.60" >> $GITHUB_ENV

- name: Download NTS extension
uses: actions/download-artifact@v4
with:
name: aikido-extension-php-${{ matrix.php_version }}-nts-x86_64
path: artifacts/extension

- name: Download agent
uses: actions/download-artifact@v4
with:
name: aikido-agent-x86_64
path: artifacts/agent

- name: Download request processor
uses: actions/download-artifact@v4
with:
name: aikido-request-processor-x86_64
path: artifacts/request-processor

- name: Download Aikido Zen Internals Lib
run: |
curl -L -O https://github.com/AikidoSec/zen-internals/releases/download/v${{ env.AIKIDO_LIBZEN_VERSION }}/libzen_internals_x86_64-unknown-linux-gnu.so

- name: Build Lambda layer zip
run: |
LAYER_DIR=lambda-layer
AIKIDO_DIR=$LAYER_DIR/aikido-${{ env.AIKIDO_VERSION }}
BREF_CONF_DIR=$LAYER_DIR/bref/etc/php/conf.d

mkdir -p $AIKIDO_DIR $BREF_CONF_DIR

find artifacts/extension -name "aikido-extension-php-${{ matrix.php_version }}-nts.so" -exec cp {} $AIKIDO_DIR/ \;
find artifacts/agent -name "aikido-agent" -exec cp {} $AIKIDO_DIR/ \;
find artifacts/request-processor -name "aikido-request-processor.so" -exec cp {} $AIKIDO_DIR/ \;
cp libzen_internals_x86_64-unknown-linux-gnu.so $AIKIDO_DIR/
chmod +x $AIKIDO_DIR/aikido-agent

echo "extension=/opt/aikido-${{ env.AIKIDO_VERSION }}/aikido-extension-php-${{ matrix.php_version }}-nts.so" > $BREF_CONF_DIR/ext-aikido.ini

ls -la $AIKIDO_DIR/

cd $LAYER_DIR
zip -r ../aikido-firewall-bref-lambda-layer-php-${{ matrix.php_version }}-x86_64.zip .

- name: Archive Lambda layer
uses: actions/upload-artifact@v4
with:
name: aikido-firewall-bref-lambda-layer-php-${{ matrix.php_version }}-x86_64
if-no-files-found: error
path: |
aikido-firewall-bref-lambda-layer-php-${{ matrix.php_version }}-x86_64.zip

build_deb:
name: Build deb ${{ matrix.arch == '' && 'x86_64' || 'arm' }}
runs-on: ubuntu-24.04${{ matrix.arch }}
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ jobs:
with:
pattern: |
aikido-php-firewall*

- name: Download Lambda layer artifacts
uses: actions/download-artifact@v4
with:
pattern: |
aikido-firewall-bref-lambda-layer-*

- name: List Artifacts
run: |
ls -l
ls -lR
pwd

- name: Deploy to GitHub Release (draft)
Expand All @@ -38,3 +44,4 @@ jobs:
draft: true
files: |
./**/aikido-php-firewall*
./**/aikido-firewall-bref-lambda-layer-*
18 changes: 16 additions & 2 deletions lib/agent/constants/constants.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
package constants

var (
IsLambda bool
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
PidPath = "/run/aikido-" + Version + "/aikido-agent.pid"
)

// SetRuntimeDir switches the socket/pid directory to /tmp when running on
// Lambda. The flag is passed from C++ via an argv (--lambda) when the agent
// is spawned.
func SetRuntimeDir(isLambda bool) {
if isLambda {
SocketPath = "/tmp/aikido-" + Version + "/aikido-agent.sock"
PidPath = "/tmp/aikido-" + Version + "/aikido-agent.pid"
}
}

const (
Version = "1.5.6"
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
PidPath = "/run/aikido-" + Version + "/aikido-agent.pid"
ConfigUpdatedAtMethod = "GET"
ConfigUpdatedAtAPI = "/config"
ConfigAPIMethod = "GET"
Expand Down
15 changes: 15 additions & 0 deletions lib/agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ var serversCleanupChannel = make(chan struct{})
var serversCleanupTicker = time.NewTicker(time.Minute)

func serversCleanupRoutine(_ *ServerData) {
// On Lambda, the agent process is frozen between invocations while
// wall-clock time keeps passing. Inactivity-based cleanup wrongly
// evicts the registered server and breaks subsequent requests.
if constants.IsLambda {
return
}
for _, serverKey := range globals.GetServersKeys() {
server := globals.GetServer(serverKey)
if server == nil {
Expand Down Expand Up @@ -88,6 +94,15 @@ func AgentUninit() {
}

func main() {
isLambda := false
for _, arg := range os.Args[1:] {
if arg == "--lambda" {
isLambda = true
break
}
}
constants.SetRuntimeDir(isLambda)

if !AgentInit() {
log.Errorf(log.MainLogger, "Agent initialization failed!")
os.Exit(-2)
Expand Down
40 changes: 33 additions & 7 deletions lib/php-extension/Agent.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#include "Includes.h"

static std::string GetRuntimeDir() {
if (IsLambda()) {
return "/tmp/aikido-" + std::string(PHP_AIKIDO_VERSION);
}
return "/run/aikido-" + std::string(PHP_AIKIDO_VERSION);
}

vector<pid_t> Agent::GetPIDsFromRunningProcesses(const std::string& aikidoAgentPath) {
vector<pid_t> agentPIDs;

Expand Down Expand Up @@ -38,13 +45,16 @@ bool Agent::Start(std::string aikidoAgentPath) {
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);

char* argv[] = {
const_cast<char*>(aikidoAgentPath.c_str()),
nullptr
};
char lambdaFlag[] = "--lambda";
std::vector<char*> argvVec;
argvVec.push_back(const_cast<char*>(aikidoAgentPath.c_str()));
if (IsLambda()) {
argvVec.push_back(lambdaFlag);
}
argvVec.push_back(nullptr);

pid_t agentPid;
int status = posix_spawn(&agentPid, aikidoAgentPath.c_str(), nullptr, &attr, argv, nullptr);
int status = posix_spawn(&agentPid, aikidoAgentPath.c_str(), nullptr, &attr, argvVec.data(), nullptr);
posix_spawnattr_destroy(&attr);
if (status != 0) {
AIKIDO_LOG_ERROR("Failed to start Aikido Agent process: %s\n", strerror(status));
Expand Down Expand Up @@ -105,7 +115,7 @@ bool Agent::IsRunning(const std::string& aikidoAgentPath, const std::string& aik

AIKIDO_LOG_INFO("Found socket file \"%s\" on disk! Checking if Aikido Agent process is running...\n", aikidoAgentSocketPath.c_str());

std::string aikidoAgentPidPath = "/run/aikido-" + std::string(PHP_AIKIDO_VERSION) + "/aikido-agent.pid";
std::string aikidoAgentPidPath = GetRuntimeDir() + "/aikido-agent.pid";
pid_t agentPIDFromFile = this->GetPIDFromFile(aikidoAgentPidPath);
vector<pid_t> agentPIDsFromRunningProcesses = this->GetPIDsFromRunningProcesses(aikidoAgentPath);
if (agentPIDFromFile == -1 ||
Expand All @@ -126,7 +136,8 @@ bool Agent::IsRunning(const std::string& aikidoAgentPath, const std::string& aik

bool Agent::Init() {
std::string aikidoAgentPath = "/opt/aikido-" + std::string(PHP_AIKIDO_VERSION) + "/aikido-agent";
std::string aikidoAgentSocketPath = "/run/aikido-" + std::string(PHP_AIKIDO_VERSION) + "/aikido-agent.sock";
std::string runtimeDir = GetRuntimeDir();
std::string aikidoAgentSocketPath = runtimeDir + "/aikido-agent.sock";

if (this->IsRunning(aikidoAgentPath, aikidoAgentSocketPath)) {
AIKIDO_LOG_INFO("Aikido Agent is already running! Skipping init...\n");
Expand All @@ -140,6 +151,21 @@ bool Agent::Init() {
return false;
}

// On Lambda cold starts, MINIT and the first invoke happen back-to-back,
// so the first gRPC call can race against agent startup. Block here
// (up to ~5s) until the agent has bound its Unix socket. On regular
// long-running SAPIs (php-fpm, apache, frankenphp) MINIT runs well
// before any request, so this wait is unnecessary.
if (IsLambda()) {
for (int i = 0; i < 1000; i++) {
if (FileExists(aikidoAgentSocketPath)) {
AIKIDO_LOG_INFO("Aikido Agent socket ready after %d ms\n", i * 5);
return true;
}
usleep(5000);
}
AIKIDO_LOG_WARN("Aikido Agent socket did not appear within 1s\n");
}
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/php-extension/RequestProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ bool RequestProcessor::Init() {
return false;
}

if (!requestProcessorInitFn(GoCreateString(AIKIDO_GLOBAL(sapi_name)))) {
if (!requestProcessorInitFn(GoCreateString(AIKIDO_GLOBAL(sapi_name)), IsLambda())) {
AIKIDO_LOG_ERROR("Failed to initialize Aikido Request Processor!\n");
this->initFailed = true;
return false;
Expand Down
4 changes: 4 additions & 0 deletions lib/php-extension/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ bool RemoveFile(const std::string& filePath) {
return false;
}

bool IsLambda() {
return getenv("AWS_LAMBDA_FUNCTION_NAME") != nullptr;
}


std::string GetStackTrace() {
#if PHP_VERSION_ID >= 80100
Expand Down
2 changes: 1 addition & 1 deletion lib/php-extension/include/RequestProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ typedef GoUint8 (*InitInstanceFn)(void* instancePtr, GoString initJson);
typedef void (*DestroyInstanceFn)(uint64_t threadId);

// Updated typedefs with instance pointer as first parameter
typedef GoUint8 (*RequestProcessorInitFn)(GoString platformName);
typedef GoUint8 (*RequestProcessorInitFn)(GoString platformName, GoUint8 isLambda);
typedef GoUint8 (*RequestProcessorContextInitFn)(void* instancePtr, ContextCallback);
typedef GoUint8 (*RequestProcessorConfigUpdateFn)(void* instancePtr, GoString initJson);
typedef char* (*RequestProcessorOnEventFn)(void* instancePtr, GoInt eventId);
Expand Down
2 changes: 2 additions & 0 deletions lib/php-extension/include/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ bool FileExists(const std::string& filePath);

bool RemoveFile(const std::string& filePath);

bool IsLambda();

std::string GetStackTrace();

zend_class_entry* GetFirewallDefaultExceptionCe();
15 changes: 13 additions & 2 deletions lib/request-processor/globals/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ func CreateServer(token string) *ServerData {
}

const (
Version = "1.5.6"
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
Version = "1.5.6"
)

var SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"

// SetRuntimeDir switches the socket directory to /tmp when running on Lambda.
// The flag is passed from C++ (via RequestProcessorInit) because Go's
// os.LookupEnv is unreliable when this shared library is loaded into a
// forked FPM worker.
func SetRuntimeDir(isLambda bool) {
if isLambda {
SocketPath = "/tmp/aikido-" + Version + "/aikido-agent.sock"
}
}
4 changes: 3 additions & 1 deletion lib/request-processor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ func DestroyInstance(threadID uint64) {
}

//export RequestProcessorInit
func RequestProcessorInit(platformName string) (initOk bool) {
func RequestProcessorInit(platformName string, isLambda bool) (initOk bool) {
defer func() {
if r := recover(); r != nil {
log.Warn(nil, "Recovered from panic:", r)
initOk = false
}
}()

globals.SetRuntimeDir(isLambda)

config.Init(platformName)

if globals.EnvironmentConfig.PlatformName != "cli" {
Expand Down
20 changes: 11 additions & 9 deletions package/rpm/aikido.spec
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ if command -v php -v >/dev/null 2>&1; then
fi

# Check common PHP installation paths
for php_path in /usr/bin/php* /usr/local/bin/php*; do
for php_path in /usr/bin/php* /usr/local/bin/php* /opt/bin/php*; do
if [[ -x "$php_path" && "$php_path" =~ php([0-9]+\.[0-9]+)$ ]]; then
version=$("$php_path" -v | grep -oP 'PHP \K\d+\.\d+' | head -n 1)
if [[ ! " ${PHP_VERSIONS[@]} " =~ " ${version} " ]]; then
Expand Down Expand Up @@ -133,9 +133,10 @@ for PHP_VERSION in "${PHP_VERSIONS[@]}"; do
fi
else
# RedHat-based system
if [ -d "$PHP_MOD_DIR" ]; then
echo "Installing new Aikido mod in $PHP_MOD_DIR/zz-aikido-%{version}.ini..."
ln -sf /opt/aikido-%{version}/aikido.ini $PHP_MOD_DIR/zz-aikido-%{version}.ini
PHP_MOD_DIR_FIRST="${PHP_MOD_DIR%%:*}"
if [ -d "$PHP_MOD_DIR_FIRST" ]; then
echo "Installing new Aikido mod in $PHP_MOD_DIR_FIRST/zz-aikido-%{version}.ini..."
ln -sf /opt/aikido-%{version}/aikido.ini $PHP_MOD_DIR_FIRST/zz-aikido-%{version}.ini
else
echo "No mod dir for PHP $PHP_VERSION! Skipping..."
continue
Expand Down Expand Up @@ -198,7 +199,7 @@ if command -v php -v >/dev/null 2>&1; then
fi

# Check common PHP installation paths
for php_path in /usr/bin/php* /usr/local/bin/php*; do
for php_path in /usr/bin/php* /usr/local/bin/php* /opt/bin/php*; do
if [[ -x "$php_path" && "$php_path" =~ php([0-9]+\.[0-9]+)$ ]]; then
version=$("$php_path" -v | grep -oP 'PHP \K\d+\.\d+' | head -n 1)
if [[ ! " ${PHP_VERSIONS[@]} " =~ " ${version} " ]]; then
Expand Down Expand Up @@ -252,10 +253,11 @@ for PHP_VERSION in "${PHP_VERSIONS[@]}"; do
rm -f $PHP_DEBIAN_MOD_DIR_APACHE2/zz-aikido-%{version}.ini
fi
else
# RedHat-based system
if [ -d "$PHP_MOD_DIR" ]; then
echo "Uninstalling Aikido mod from $PHP_MOD_DIR/zz-aikido-%{version}.ini..."
rm -f $PHP_MOD_DIR/zz-aikido-%{version}.ini
# RedHat-based system (take first dir if colon-separated)
PHP_MOD_DIR_FIRST="${PHP_MOD_DIR%%:*}"
if [ -f "$PHP_MOD_DIR_FIRST/zz-aikido-%{version}.ini" ]; then
echo "Uninstalling Aikido mod from $PHP_MOD_DIR_FIRST/zz-aikido-%{version}.ini..."
rm -f $PHP_MOD_DIR_FIRST/zz-aikido-%{version}.ini
fi
fi

Expand Down
Loading