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
20 changes: 14 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:

- name: Run tests with coverage
run: |
# Intentionally non-gating: real aggregate coverage has not yet reached
# the 98% target here (tracked separately). Do not unmask until the
# underlying test-coverage work lands.
python3 -m pytest dns-server/tests \
--cov=dns-server/app \
--cov-report=xml:coverage.xml --cov-report=term-missing \
Expand Down Expand Up @@ -155,17 +158,22 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}

- name: Run bandit (Python security scanner)
- name: Run bandit (Python security scanner, JSON report)
run: |
pip install bandit[toml]
bandit -r . --format json --output bandit-results.json || true
bandit -r dns-server/app manager/backend/app squawk-client/bins dhcp-server/app ntp-server/bins \
-ll --format json --output bandit-results.json || true
continue-on-error: true

- name: Run bandit (Python security scanner, gating)
run: |
bandit -r dns-server/app manager/backend/app squawk-client/bins dhcp-server/app ntp-server/bins -ll

- name: Run gosec (Go security scanner)
uses: securego/gosec@223e19b8856e00f02cc67804499a83f77e208f3c # v2.25.0
with:
args: '-no-fail -fmt json -out gosec-results.json ./...'
continue-on-error: true
working-directory: squawk-client-go
run: |
go install github.com/securego/gosec/v2/cmd/gosec@v2.25.0
gosec ./...

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: CodeQL

on:
push:
branches: [ main, 'v2.1.x', 'release/**' ]
pull_request:
branches: [ main, 'v2.1.x', 'release/**' ]
schedule:
- cron: '23 4 * * 1' # weekly, Monday 04:23 UTC

permissions:
security-events: write
actions: read
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- language: python
build-mode: none
- language: go
build-mode: autobuild
- language: javascript-typescript
build-mode: none

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Set up Go
if: matrix.language == 'go'
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version: '1.25.12'
cache: false

- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

# For build-mode: autobuild (go), the init step above builds the source
# automatically. Interpreted languages (python, javascript-typescript)
# use build-mode: none and require no separate build step.

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
category: '/language:${{ matrix.language }}'
3 changes: 3 additions & 0 deletions .github/workflows/server-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:

- name: Run tests with coverage
run: |
# Intentionally non-gating: real aggregate coverage has not yet reached
# the 98% target here (tracked separately). Do not unmask until the
# underlying test-coverage work lands.
python3 -m pytest dns-server/tests \
--cov=dns-server/app \
--cov-report=xml:coverage.xml --cov-report=term-missing \
Expand Down
23 changes: 13 additions & 10 deletions .github/workflows/version-monitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,22 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}

- name: Run bandit
- name: Run bandit (JSON report)
run: |
pip install bandit[toml]
bandit -r . --format json --output bandit-results.json || true
bandit -r dns-server/app manager/backend/app squawk-client/bins dhcp-server/app ntp-server/bins \
-ll --format json --output bandit-results.json || true
continue-on-error: true

- name: Run gosec
uses: securego/gosec@223e19b8856e00f02cc67804499a83f77e208f3c # v2.25.0
with:
args: '-no-fail -fmt json -out gosec-results.json ./...'
continue-on-error: true
- name: Run bandit (gating)
run: |
bandit -r dns-server/app manager/backend/app squawk-client/bins dhcp-server/app ntp-server/bins -ll

- name: Run gosec (gating)
working-directory: squawk-client-go
run: |
go install github.com/securego/gosec/v2/cmd/gosec@v2.25.0
gosec ./...

- name: Report security scan summary
run: |
Expand All @@ -135,6 +140,4 @@ jobs:
echo "Python Security (bandit): Scanned"
fi

if [ -f gosec-results.json ]; then
echo "Go Security (gosec): Scanned"
fi
echo "Go Security (gosec): Scanned"
2 changes: 1 addition & 1 deletion manager/backend/app/models/dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def define_dhcp_tables(db):
db.define_table('dhcp_server',
Field('name', 'string', notnull=True, length=100),
Field('hostname', 'string', length=255),
Field('listen_address', 'string', length=50, default='0.0.0.0'),
Field('listen_address', 'string', length=50, default='0.0.0.0'), # nosec B104 - DHCP server config default, not an application socket bind
Field('status', 'string', notnull=True, default='offline',
requires=lambda value: value in ['online', 'offline', 'degraded']),
Field('last_heartbeat', 'datetime'),
Expand Down
2 changes: 1 addition & 1 deletion manager/backend/app/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@
Column("id", Integer, primary_key=True, autoincrement=True),
Column("name", String(100), nullable=False),
Column("hostname", String(255)),
Column("listen_address", String(50), server_default="0.0.0.0"),
Column("listen_address", String(50), server_default="0.0.0.0"), # nosec B104 - DHCP server config default, not an application socket bind
Column("status", String(20), nullable=False, server_default="offline"),
Column("last_heartbeat", DateTime),
Column("version", String(50)),
Expand Down
4 changes: 2 additions & 2 deletions ntp-server/bins/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@
# Create server socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("0.0.0.0", self.port))
server_socket.bind(("0.0.0.0", self.port)) # nosec B104 - NTP server is a network daemon that must listen on all interfaces to serve clients
server_socket.listen(5)
server_socket.setblocking(False)

Expand Down Expand Up @@ -1079,7 +1079,7 @@
loop = asyncio.get_event_loop()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", self.port))
sock.bind(("0.0.0.0", self.port)) # nosec B104 - NTP server is a network daemon that must listen on all interfaces to serve clients
sock.setblocking(False)

logger.info(f"UDP NTP server listening on port {self.port}")
Expand Down
2 changes: 1 addition & 1 deletion squawk-client-go/pkg/dhcp/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func (i *Interceptor) buildResponsePacket(request *DHCPMessage, lease *Lease, ms
// DNS servers
if len(lease.DNSServers) > 0 {
packet[offset] = OptDNS
packet[offset+1] = byte(len(lease.DNSServers) * 4) //nolint:gosec // G115: len(DNSServers)*4 is always < 256 (max 6 servers * 4 = 24)
packet[offset+1] = byte(len(lease.DNSServers) * 4) // #nosec G115 -- len(DNSServers)*4 is always < 256 (max 6 servers * 4 = 24)
offset += 2
for _, dns := range lease.DNSServers {
dnsIP := net.ParseIP(dns)
Expand Down
4 changes: 2 additions & 2 deletions squawk-client-go/pkg/ntp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ func ParseNTPPacket(data []byte) (*NTPPacket, error) {
Settings: data[0],
Stratum: data[1],
Poll: data[2],
Precision: int8(data[3]), //nolint:gosec // G115: NTP precision field is signed byte
Precision: int8(data[3]), // #nosec G115 -- NTP precision field is signed byte
RootDelay: binary.BigEndian.Uint32(data[4:8]),
RootDispersion: binary.BigEndian.Uint32(data[8:12]),
ReferenceID: binary.BigEndian.Uint32(data[12:16]),
Expand All @@ -347,7 +347,7 @@ func EncodeNTPPacket(packet *NTPPacket) []byte {
data[0] = packet.Settings
data[1] = packet.Stratum
data[2] = packet.Poll
data[3] = byte(packet.Precision) //nolint:gosec // G115: NTP precision field is converted back to byte
data[3] = byte(packet.Precision) // #nosec G115 -- NTP precision field is converted back to byte

binary.BigEndian.PutUint32(data[4:8], packet.RootDelay)
binary.BigEndian.PutUint32(data[8:12], packet.RootDispersion)
Expand Down
2 changes: 1 addition & 1 deletion squawk-client-go/pkg/ntp/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (i *Interceptor) buildResponse(request *NTPPacket, result *TimeResult) *NTP

response := &NTPPacket{
Settings: 0x24, // LI=0, VN=4, Mode=4 (Server)
Stratum: byte(result.Stratum), //nolint:gosec // G115: NTP Stratum is 0-16, safe conversion
Stratum: byte(result.Stratum), // #nosec G115 -- NTP Stratum is 0-16, safe conversion
Poll: 4, // 16 seconds minimum
Precision: -20, // ~1 microsecond
RootDelay: uint32(result.Delay.Seconds() * 65536),
Expand Down
2 changes: 1 addition & 1 deletion squawk-client/bins/k8s-client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def resolve(self, name):
"Authorization": f"Bearer {self.token}",
}
params = {"name": name, "type": "A"}
response = requests.get(self.doh_url, headers=headers, params=params)
response = requests.get(self.doh_url, headers=headers, params=params, timeout=10)
response.raise_for_status()
return response.json()

Expand Down
Loading