From e49e5d51184d518afa5413b4a25ca8ac3290cc97 Mon Sep 17 00:00:00 2001 From: shahargl Date: Sun, 24 Nov 2024 20:41:52 +0200 Subject: [PATCH 1/3] feat: wip --- keep-ui/app/api/auth/[...nextauth]/route.ts | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/keep-ui/app/api/auth/[...nextauth]/route.ts b/keep-ui/app/api/auth/[...nextauth]/route.ts index 7c62e2db1..be6543d31 100644 --- a/keep-ui/app/api/auth/[...nextauth]/route.ts +++ b/keep-ui/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,23 @@ import { handlers } from "@/auth"; -export const { GET, POST } = handlers; +import { NextRequest } from "next/server"; + +const reqWithTrustedOrigin = (req: NextRequest): NextRequest => { + if (process.env.AUTH_TRUST_HOST !== "true") return req; + const proto = req.headers.get("x-forwarded-proto"); + const host = req.headers.get("x-forwarded-host"); + if (!proto || !host) { + console.warn("Missing x-forwarded-proto or x-forwarded-host headers."); + return req; + } + const envOrigin = `${proto}://${host}`; + const { href, origin } = req.nextUrl; + return new NextRequest(href.replace(origin, envOrigin), req); +}; + +export const GET = (req: NextRequest) => { + return handlers.GET(reqWithTrustedOrigin(req)); +}; + +export const POST = (req: NextRequest) => { + return handlers.POST(reqWithTrustedOrigin(req)); +}; From 219c4003fbb8a5cfa8d3c4623a156e708307e973 Mon Sep 17 00:00:00 2001 From: shahargl Date: Mon, 25 Nov 2024 09:48:18 +0200 Subject: [PATCH 2/3] feat: wip --- keep-ui/auth.config.ts | 274 +++++++++++++++++++++++++++++++++++ keep-ui/auth.ts | 318 ++--------------------------------------- keep-ui/middleware.tsx | 22 +-- 3 files changed, 299 insertions(+), 315 deletions(-) create mode 100644 keep-ui/auth.config.ts diff --git a/keep-ui/auth.config.ts b/keep-ui/auth.config.ts new file mode 100644 index 000000000..5ec968059 --- /dev/null +++ b/keep-ui/auth.config.ts @@ -0,0 +1,274 @@ +import type { NextAuthConfig } from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import Keycloak from "next-auth/providers/keycloak"; +import Auth0 from "next-auth/providers/auth0"; +import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; +import { AuthError } from "next-auth"; +import { AuthenticationError, AuthErrorCodes } from "@/errors"; +import type { JWT } from "next-auth/jwt"; +import type { User } from "next-auth"; +import { getApiURL } from "@/utils/apiUrl"; +import { + AuthType, + MULTI_TENANT, + SINGLE_TENANT, + NO_AUTH, + NoAuthUserEmail, + NoAuthTenant, +} from "@/utils/authenticationType"; + +export class BackendRefusedError extends AuthError { + static type = "BackendRefusedError"; +} + +// Determine auth type with backward compatibility +const authTypeEnv = process.env.AUTH_TYPE; +export const authType = + authTypeEnv === MULTI_TENANT + ? AuthType.AUTH0 + : authTypeEnv === SINGLE_TENANT + ? AuthType.DB + : authTypeEnv === NO_AUTH + ? AuthType.NOAUTH + : (authTypeEnv as AuthType); + +async function refreshAccessToken(token: any) { + const issuerUrl = process.env.KEYCLOAK_ISSUER; + const refreshTokenUrl = `${issuerUrl}/protocol/openid-connect/token`; + + try { + const response = await fetch(refreshTokenUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + client_id: process.env.KEYCLOAK_ID!, + client_secret: process.env.KEYCLOAK_SECRET!, + grant_type: "refresh_token", + refresh_token: token.refreshToken, + }), + }); + + const refreshedTokens = await response.json(); + + if (!response.ok) { + throw new Error( + `Refresh token failed: ${response.status} ${response.statusText}` + ); + } + + return { + ...token, + accessToken: refreshedTokens.access_token, + accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, + refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, + }; + } catch (error) { + console.error("Error refreshing access token:", error); + return { + ...token, + error: "RefreshAccessTokenError", + }; + } +} + +// Base provider configurations without AzureAD +const baseProviderConfigs = { + [AuthType.AUTH0]: [ + Auth0({ + clientId: process.env.AUTH0_CLIENT_ID!, + clientSecret: process.env.AUTH0_CLIENT_SECRET!, + issuer: process.env.AUTH0_ISSUER!, + authorization: { + params: { + prompt: "login", + }, + }, + }), + ], + [AuthType.DB]: [ + Credentials({ + name: "Credentials", + credentials: { + username: { label: "Username", type: "text", placeholder: "keep" }, + password: { label: "Password", type: "password", placeholder: "keep" }, + }, + async authorize(credentials): Promise { + try { + const response = await fetch(`${getApiURL()}/signin`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(credentials), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + console.error("Authentication failed:", errorData); + throw new AuthenticationError(AuthErrorCodes.INVALID_CREDENTIALS); + } + + const user = await response.json(); + if (!user.accessToken) return null; + + return { + id: user.id, + name: user.name, + email: user.email, + accessToken: user.accessToken, + tenantId: user.tenantId, + role: user.role, + }; + } catch (error) { + if (error instanceof TypeError && error.message === "fetch failed") { + throw new AuthenticationError(AuthErrorCodes.CONNECTION_REFUSED); + } + + if (error instanceof AuthenticationError) { + throw error; + } + + throw new AuthenticationError(AuthErrorCodes.SERVICE_UNAVAILABLE); + } + }, + }), + ], + [AuthType.NOAUTH]: [ + Credentials({ + name: "NoAuth", + credentials: {}, + async authorize(): Promise { + return { + id: "keep-user-for-no-auth-purposes", + name: "Keep", + email: NoAuthUserEmail, + accessToken: "keep-token-for-no-auth-purposes", + tenantId: NoAuthTenant, + role: "user", + }; + }, + }), + ], + [AuthType.KEYCLOAK]: [ + Keycloak({ + clientId: process.env.KEYCLOAK_ID!, + clientSecret: process.env.KEYCLOAK_SECRET!, + issuer: process.env.KEYCLOAK_ISSUER, + authorization: { params: { scope: "openid email profile roles" } }, + }), + ], + [AuthType.AZUREAD]: [ + MicrosoftEntraID({ + clientId: process.env.KEEP_AZUREAD_CLIENT_ID!, + clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!, + issuer: `https://login.microsoftonline.com/${process.env + .KEEP_AZUREAD_TENANT_ID!}/v2.0`, + authorization: { + params: { + scope: `api://${process.env + .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`, + }, + }, + client: { + token_endpoint_auth_method: "client_secret_post", + }, + }), + ], +}; + +export const config = { + trustHost: true, + providers: + baseProviderConfigs[authType as keyof typeof baseProviderConfigs] || + baseProviderConfigs[AuthType.NOAUTH], + pages: { + signIn: "/signin", + }, + session: { + strategy: "jwt" as const, + maxAge: 30 * 24 * 60 * 60, // 30 days + }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + const isOnDashboard = nextUrl.pathname.startsWith("/dashboard"); + if (isOnDashboard) { + if (isLoggedIn) return true; + return false; + } + return true; + }, + jwt: async ({ token, user, account, profile }): Promise => { + if (account && user) { + let accessToken: string | undefined; + let tenantId: string | undefined = user.tenantId; + let role: string | undefined = user.role; + + if (authType === AuthType.AZUREAD) { + accessToken = account.access_token; + if (account.id_token) { + try { + const payload = JSON.parse( + Buffer.from(account.id_token.split(".")[1], "base64").toString() + ); + role = payload.roles?.[0] || "user"; + tenantId = payload.tid || undefined; + } catch (e) { + console.warn("Failed to decode id_token:", e); + } + } + } else if (authType == AuthType.AUTH0) { + accessToken = account.id_token; + if ((profile as any)?.keep_tenant_id) { + tenantId = (profile as any).keep_tenant_id; + } + if ((profile as any)?.keep_role) { + role = (profile as any).keep_role; + } + } else { + accessToken = + user.accessToken || account.access_token || account.id_token; + } + if (!accessToken) { + throw new Error("No access token available"); + } + + token.accessToken = accessToken; + token.tenantId = tenantId; + token.role = role; + + if (authType === AuthType.KEYCLOAK) { + token.refreshToken = account.refresh_token; + token.accessTokenExpires = + Date.now() + (account.expires_in as number) * 1000; + } + } else if ( + authType === AuthType.KEYCLOAK && + token.accessTokenExpires && + typeof token.accessTokenExpires === "number" && + Date.now() < token.accessTokenExpires + ) { + token = await refreshAccessToken(token); + if (!token.accessToken) { + throw new Error("Failed to refresh access token"); + } + } + + return token; + }, + session: async ({ session, token, user }) => { + return { + ...session, + accessToken: token.accessToken as string, + tenantId: token.tenantId as string, + userRole: token.role as string, + user: { + ...session.user, + accessToken: token.accessToken as string, + tenantId: token.tenantId as string, + role: token.role as string, + }, + }; + }, + }, +} satisfies NextAuthConfig; diff --git a/keep-ui/auth.ts b/keep-ui/auth.ts index f0b470971..6c2cb25a6 100644 --- a/keep-ui/auth.ts +++ b/keep-ui/auth.ts @@ -1,40 +1,9 @@ import NextAuth from "next-auth"; -import type { NextAuthConfig } from "next-auth"; import { customFetch } from "next-auth"; -import Credentials from "next-auth/providers/credentials"; -import Keycloak from "next-auth/providers/keycloak"; -import Auth0 from "next-auth/providers/auth0"; +import { config, authType } from "@/auth.config"; +import { ProxyAgent, fetch as undici } from "undici"; import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; -import { AuthError } from "next-auth"; -import { AuthenticationError, AuthErrorCodes } from "@/errors"; -import type { JWT } from "next-auth/jwt"; -// https://github.com/nextauthjs/next-auth/issues/11028 - -export class BackendRefusedError extends AuthError { - static type = "BackendRefusedError"; -} - -import { getApiURL } from "@/utils/apiUrl"; -import { - AuthType, - MULTI_TENANT, - SINGLE_TENANT, - NO_AUTH, - NoAuthUserEmail, - NoAuthTenant, -} from "@/utils/authenticationType"; -import type { User } from "next-auth"; - -// Determine auth type with backward compatibility -const authTypeEnv = process.env.AUTH_TYPE; -const authType = - authTypeEnv === MULTI_TENANT - ? AuthType.AUTH0 - : authTypeEnv === SINGLE_TENANT - ? AuthType.DB - : authTypeEnv === NO_AUTH - ? AuthType.NOAUTH - : (authTypeEnv as AuthType); +import { AuthType } from "@/utils/authenticationType"; const proxyUrl = process.env.HTTP_PROXY || @@ -42,7 +11,6 @@ const proxyUrl = process.env.http_proxy || process.env.https_proxy; -import { ProxyAgent, fetch as undici } from "undici"; function proxyFetch( ...args: Parameters ): ReturnType { @@ -68,41 +36,17 @@ function proxyFetch( return undici(args[0], { ...(args[1] || {}), dispatcher }); } -/** - * Creates a Microsoft Entra ID provider configuration and overrides the customFetch. - * - * SHAHAR: this is a workaround to override the customFetch symbol in the provider - * because in Microsoft entra it already has a customFetch symbol and we need to override it.s - */ -export const createAzureADProvider = () => { +// Modify the config if using Azure AD with proxy +if (authType === AuthType.AZUREAD && proxyUrl) { + const provider = config.providers[0] as ReturnType; + if (!proxyUrl) { - console.log("Proxy is not enabled"); + console.log("Proxy is not enabled for Azure AD"); } else { - console.log("Proxy is enabled:", proxyUrl); + console.log("Proxy is enabled for Azure AD:", proxyUrl); } - // Step 1: Create the base provider - const baseConfig = { - clientId: process.env.KEEP_AZUREAD_CLIENT_ID!, - clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!, - issuer: `https://login.microsoftonline.com/${process.env - .KEEP_AZUREAD_TENANT_ID!}/v2.0`, - authorization: { - params: { - scope: `api://${process.env - .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`, - }, - }, - client: { - token_endpoint_auth_method: "client_secret_post", - }, - }; - - const provider = MicrosoftEntraID(baseConfig); - // if not proxyUrl, return the provider - if (!proxyUrl) return provider; - - // Step 2: Override the `customFetch` symbol in the provider + // Override the `customFetch` symbol in the provider provider[customFetch] = async (...args: Parameters) => { const url = new URL(args[0] instanceof Request ? args[0].url : args[0]); console.log("Custom Fetch Intercepted:", url.toString()); @@ -113,7 +57,7 @@ export const createAzureADProvider = () => { const response = await proxyFetch(...args); const json = await response.clone().json(); const tenantRe = /microsoftonline\.com\/(\w+)\/v2\.0/; - const tenantId = baseConfig.issuer?.match(tenantRe)?.[1] ?? "common"; + const tenantId = provider.issuer?.match(tenantRe)?.[1] ?? "common"; const issuer = json.issuer.replace("{tenantid}", tenantId); console.log("Modified issuer:", issuer); return Response.json({ ...json, issuer }); @@ -123,9 +67,9 @@ export const createAzureADProvider = () => { return proxyFetch(...args); }; - // Step 3: override profile since it use fetch without customFetch + // Override profile since it uses fetch without customFetch provider.profile = async (profile, tokens) => { - const profilePhotoSize = 48; // Default or custom size + const profilePhotoSize = 48; console.log("Fetching profile photo via proxy"); const response = await proxyFetch( @@ -144,250 +88,16 @@ export const createAzureADProvider = () => { } } - // Ensure the returned object matches the User interface return { id: profile.sub, name: profile.name, email: profile.email, image: image ?? null, - accessToken: tokens.access_token ?? "", // Provide empty string as fallback + accessToken: tokens.access_token ?? "", }; }; - - return provider; -}; - -async function refreshAccessToken(token: any) { - const issuerUrl = process.env.KEYCLOAK_ISSUER; - const refreshTokenUrl = `${issuerUrl}/protocol/openid-connect/token`; - - try { - const response = await fetch(refreshTokenUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - client_id: process.env.KEYCLOAK_ID!, - client_secret: process.env.KEYCLOAK_SECRET!, - grant_type: "refresh_token", - refresh_token: token.refreshToken, - }), - }); - - const refreshedTokens = await response.json(); - - if (!response.ok) { - throw new Error( - `Refresh token failed: ${response.status} ${response.statusText}` - ); - } - - return { - ...token, - accessToken: refreshedTokens.access_token, - accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, - refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, - }; - } catch (error) { - console.error("Error refreshing access token:", error); - return { - ...token, - error: "RefreshAccessTokenError", - }; - } } -// Define provider configurations -const providerConfigs = { - [AuthType.AUTH0]: [ - Auth0({ - clientId: process.env.AUTH0_CLIENT_ID!, - clientSecret: process.env.AUTH0_CLIENT_SECRET!, - issuer: process.env.AUTH0_ISSUER!, - authorization: { - params: { - prompt: "login", - }, - }, - }), - ], - [AuthType.DB]: [ - Credentials({ - name: "Credentials", - credentials: { - username: { label: "Username", type: "text", placeholder: "keep" }, - password: { label: "Password", type: "password", placeholder: "keep" }, - }, - async authorize(credentials): Promise { - try { - const response = await fetch(`${getApiURL()}/signin`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(credentials), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - console.error("Authentication failed:", errorData); - throw new AuthenticationError(AuthErrorCodes.INVALID_CREDENTIALS); - } - - const user = await response.json(); - if (!user.accessToken) return null; - - return { - id: user.id, - name: user.name, - email: user.email, - accessToken: user.accessToken, - tenantId: user.tenantId, - role: user.role, - }; - } catch (error) { - if (error instanceof TypeError && error.message === "fetch failed") { - throw new AuthenticationError(AuthErrorCodes.CONNECTION_REFUSED); - } - - if (error instanceof AuthenticationError) { - throw error; - } - - throw new AuthenticationError(AuthErrorCodes.SERVICE_UNAVAILABLE); - } - }, - }), - ], - [AuthType.NOAUTH]: [ - Credentials({ - name: "NoAuth", - credentials: {}, - async authorize(): Promise { - return { - id: "keep-user-for-no-auth-purposes", - name: "Keep", - email: NoAuthUserEmail, - accessToken: "keep-token-for-no-auth-purposes", - tenantId: NoAuthTenant, - role: "user", - }; - }, - }), - ], - [AuthType.KEYCLOAK]: [ - Keycloak({ - clientId: process.env.KEYCLOAK_ID!, - clientSecret: process.env.KEYCLOAK_SECRET!, - issuer: process.env.KEYCLOAK_ISSUER, - authorization: { params: { scope: "openid email profile roles" } }, - }), - ], - [AuthType.AZUREAD]: [createAzureADProvider()], -}; - -// Create the config -const config = { - trustHost: true, - providers: - providerConfigs[authType as keyof typeof providerConfigs] || - providerConfigs[AuthType.NOAUTH], - pages: { - signIn: "/signin", - }, - session: { - strategy: "jwt" as const, - maxAge: 30 * 24 * 60 * 60, // 30 days - }, - callbacks: { - authorized({ auth, request: { nextUrl } }) { - const isLoggedIn = !!auth?.user; - const isOnDashboard = nextUrl.pathname.startsWith("/dashboard"); - if (isOnDashboard) { - if (isLoggedIn) return true; - return false; - } - return true; - }, - jwt: async ({ token, user, account, profile }): Promise => { - if (account && user) { - let accessToken: string | undefined; - let tenantId: string | undefined = user.tenantId; - let role: string | undefined = user.role; - // Ensure we always have an accessToken - // https://github.com/nextauthjs/next-auth/discussions/4255 - if (authType === AuthType.AZUREAD) { - // Properly handle Azure AD tokens - accessToken = account.access_token; - // You might want to extract additional claims from the id_token if needed - if (account.id_token) { - try { - // Basic JWT decode (you might want to use a proper JWT library here) - const payload = JSON.parse( - Buffer.from(account.id_token.split(".")[1], "base64").toString() - ); - // Extract any additional claims you need - role = payload.roles?.[0] || "user"; - tenantId = payload.tid || undefined; - } catch (e) { - console.warn("Failed to decode id_token:", e); - } - } - } else if (authType == AuthType.AUTH0) { - accessToken = account.id_token; - if ((profile as any)?.keep_tenant_id) { - tenantId = (profile as any).keep_tenant_id; - } - if ((profile as any)?.keep_role) { - role = (profile as any).keep_role; - } - } else { - accessToken = - user.accessToken || account.access_token || account.id_token; - } - if (!accessToken) { - throw new Error("No access token available"); - } - - token.accessToken = accessToken; - token.tenantId = tenantId; - token.role = role; - - if (authType === AuthType.KEYCLOAK) { - token.refreshToken = account.refresh_token; - token.accessTokenExpires = - Date.now() + (account.expires_in as number) * 1000; - } - } else if ( - authType === AuthType.KEYCLOAK && - token.accessTokenExpires && - typeof token.accessTokenExpires === "number" && - Date.now() < token.accessTokenExpires - ) { - token = await refreshAccessToken(token); - if (!token.accessToken) { - throw new Error("Failed to refresh access token"); - } - } - - return token; - }, - session: async ({ session, token, user }) => { - return { - ...session, - accessToken: token.accessToken as string, - tenantId: token.tenantId as string, - userRole: token.role as string, - user: { - ...session.user, - accessToken: token.accessToken as string, - tenantId: token.tenantId as string, - role: token.role as string, - }, - }; - }, - }, -} satisfies NextAuthConfig; - console.log("Starting Keep frontend with auth type:", authType); export const { handlers, auth, signIn, signOut } = NextAuth(config); diff --git a/keep-ui/middleware.tsx b/keep-ui/middleware.tsx index 78e82ef4b..ccdaa8355 100644 --- a/keep-ui/middleware.tsx +++ b/keep-ui/middleware.tsx @@ -2,20 +2,20 @@ import { NextResponse, type NextRequest } from "next/server"; import { getToken } from "next-auth/jwt"; import type { JWT } from "next-auth/jwt"; import { getApiURL } from "@/utils/apiUrl"; +import { config as authConfig } from "@/auth.config"; +import NextAuth from "next-auth"; + +const { auth } = NextAuth(authConfig); export async function middleware(request: NextRequest) { const { pathname, searchParams } = request.nextUrl; + const session = await auth(); + const role = session?.userRole; + const isAuthenticated = !!session; // Keep it on header so it can be used in server components const requestHeaders = new Headers(request.headers); requestHeaders.set("x-url", request.url); - - // Get the token using next-auth/jwt with the correct type - const token = (await getToken({ - req: request, - secret: process.env.NEXTAUTH_SECRET, - })) as JWT | null; - // Handle legacy /backend/ redirects if (pathname.startsWith("/backend/")) { const apiUrl = getApiURL(); @@ -33,13 +33,13 @@ export async function middleware(request: NextRequest) { } // If not authenticated and not on signin page, redirect to signin - if (!token && !pathname.startsWith("/signin")) { + if (!isAuthenticated && !pathname.startsWith("/signin")) { console.log("Redirecting to signin page because user is not authenticated"); return NextResponse.redirect(new URL("/signin", request.url)); } - // If authenticated and on signin page, redirect to dashboard - if (token && pathname.startsWith("/signin")) { + // If authenticated and on signin page, redirect to incidents + if (isAuthenticated && pathname.startsWith("/signin")) { console.log( "Redirecting to incidents because user try to get /signin but already authenticated" ); @@ -47,7 +47,7 @@ export async function middleware(request: NextRequest) { } // Role-based routing (NOC users) - if (token?.role === "noc" && !pathname.startsWith("/alerts")) { + if (role === "noc" && !pathname.startsWith("/alerts")) { return NextResponse.redirect(new URL("/alerts/feed", request.url)); } From 177af4f7ec6fc408a629cb0da7729451a20bb9d2 Mon Sep 17 00:00:00 2001 From: shahargl Date: Mon, 25 Nov 2024 09:54:54 +0200 Subject: [PATCH 3/3] feat: new auth --- proxy/squid.conf | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/proxy/squid.conf b/proxy/squid.conf index e36a34687..2e569360f 100644 --- a/proxy/squid.conf +++ b/proxy/squid.conf @@ -1,26 +1,31 @@ # Port configurations http_port 3128 -dns_nameservers 8.8.8.8 8.8.4.4 # Google DNS servers, adjust as needed # DNS configurations +dns_nameservers 8.8.8.8 8.8.4.4 dns_v4_first on -dns_timeout 5 seconds -positive_dns_ttl 24 hours -negative_dns_ttl 1 minutes -# Allow all clients in our Docker network -acl localnet src 172.16.0.0/12 +# ACL definitions +acl SSL_ports port 443 +acl Safe_ports port 80 # http +acl Safe_ports port 443 # https +acl Safe_ports port 1025-65535 # unprivileged ports +acl CONNECT method CONNECT +acl localnet src 172.16.0.0/12 # Docker network + +# Access rules - order is important +http_access deny !Safe_ports +http_access deny CONNECT !SSL_ports +http_access allow localnet http_access allow all -# Basic settings +# Logging +debug_options ALL,1 28,3 + +# Cache settings cache_dir ufs /var/spool/squid 100 16 256 coredump_dir /var/spool/squid -# DNS cache settings -ipcache_size 1024 -ipcache_low 90 -ipcache_high 95 - # Refresh patterns refresh_pattern ^ftp: 1440 20% 10080 refresh_pattern ^gopher: 1440 0% 1440