보안 규칙 언어

GonnabeAlright·2022년 1월 16일
0
post-thumbnail

Firebase 보안 규칙은 다양한 복잡성과 세분화 범위를 지원하는 유연하고 강력한 커스텀 언어를 활용합니다. 앱에 적합하도록 구체적이거나 일반적인 규칙을 만들 수 있습니다. 실시간 데이터베이스 규칙은 JSON 구조에서 자바스크립트와 유사한 구문을 사용합니다. Cloud Firestore 및 Cloud Storage 규칙은 CEL(Common Expression Language) 기반 언어를 사용하며, 조건부로 부여된 액세스 권한을 지원하는 matchallow 문을 사용하여 CEL에 빌드합니다.

하지만 커스텀 언어이기 때문에 사용법을 익혀야 합니다. 복잡한 규칙을 익혀 나가는 과정에서 규칙 언어를 보다 잘 이해할 수 있도록 이 가이드를 활용하시기 바랍니다.

앞으로 출시될 앱은 실시간 데이터베이스(Realtime database)를 사용하지 않고 Cloud Firestore 및 Cloud Storage를 사용하기에 실시간 데이터베이스는 생략하였습니다.

service <<name>> {
  // Match the resource path.
  match <<path>> {
  // Allow the request if the following condition are true.
  	allow <<methods>>: if <<condition>>
  }
}

다음 핵심 개념은 규칙을 작성할 때 반드시 숙지해야 합니다.

  • 요청: allow 문에서 호출되는 메서드이며, 실행하도록 지정할 수 있는 메서드입니다. 표준 메서드는 get, list, create, update, delete입니다. 편리한 readwrite 메서드를 통해 지정된 데이터베이스 또는 스토리지 경로에서 광범위한 읽기 및 쓰기 액세스가 가능합니다.
  • 경로: URI 경로로 표시되는 데이터베이스 또는 스토리지 위치입니다.
  • 규칙: true로 평가되면 요청을 허용하는 조건을 포함하는 allow문입니다.

Cloud Firestore 및 Cloud Storage에서 규칙의 기본 요소는 다음과 같습니다.

  • service 선언: 규칙이 적용되는 Firebase 제품을 선언합니다.
  • match 블록: 규칙이 적용되는 데이터베이스 또는 스토리지 버킷의 경로를 정의합니다.
  • allow문: 메서드별로 구분한 액세스 권한을 부여하도록 조건을 제공합니다. 지원되는 메서드는 get, list, create, update, delete이며, 편리한 메서드는 readwrite 입니다.
  • 선택적 function 선언: 여러 규칙에서 사용하기 위해 조건을 결합하고 래핑할 수 있는 기능을 제공합니다.

service에는 하나 이상의 match 블록과 요청에 대한 액세스 허용 조건을 제시하는 allow 문이 포함됩니다. request 및 resource 변수는 규칙 조건에서 사용할 수 있습니다. Firebase 보안 규칙 언어는 function 선언도 지원합니다.

구문 버전

syntax문은 소스를 작성하는 데 사용되는 Firebase 규칙 언어의 버전을 나타냅니다. 최신 언어 버전은 v2입니다.

rules_version = '2';
service cloud.firestore {
 ... 
}

rules_version 문이 제공되지 않으면 규칙이 v1 엔진을 사용하여 평가됩니다.

서비스

service 선언은 규칙이 적용되는 Firebase 제품 또는 서비스를 정의합니다. 소스 파일당 하나의 service 선언만 포함할 수 있습니다.

service cloud.firestore {
  // Your 'match' blocks with their corresponding 'allow' statements and
  // optional 'function' declarations are contained here
}

Firebase CLI를 사용하여 Cloud Firestore 및 Cloud Storage 양쪽에 규칙을 정의하는 경우 별도의 파일로 규칙을 관리해야 합니다.

일치

match 블록은 요청된 작업(수신 request.path)의 경로와 일치하는 path 패턴을 선언합니다. match의 본문에는 하나 이상의 중첩된 match 블록, allow 문 또는 function 선언이 있어야 합니다. 중첩된 match 블록의 경로는 상위 match 블록의 경로를 기준을 합니다.

path 패턴은 변수 또는 와일드 카드를 포함할 수 있는 디렉토리와 유사한 이름입니다. path 패턴단일 경로 세그먼트다중 경로 세그먼트 일치를 허용합니다. path에 바인딩된 모든 변수는 match 범위 또는 path가 선언된 모든 중첩된 범위에서 볼 수 있습니다.

path 패턴과의 일치는 부분적이거나 완전할 수 있습니다.

  • 부분 일치: path 패턴이 request.path의 프리픽스와 일치합니다.
  • 전체 일치: path 패턴이 전체 request.path와 일치합니다.

전체 일치인 경우 블록 내의 규칙이 평가됩니다. 부분 일치인 경우 중첩된 match 규칙을 테스트하여 완전히 일치하는 중첩된 path가 있는지 확인합니다.

각 전체 match의 규칙은 요청을 허용할지 여부를 판단하기 위해 평가됩니다. 액세스를 허용하는 일치 규칙이 있으면 요청이 허용되고, 액세스를 허용하는 일치 규칙이 없으면 요청이 거부됩니다.

// Given request.path == /example/hello/nested/path the following
// declarations indicate whether they are a partial or complete match and 
// the value of any variables visible within the scope.
service firebase.storage {
  // Partial match.
  // 'singleSegment' == 'hello'
  match /example/{singleSegment} {   
    allow write:		// Write rule not evaluated.
    // Complete match.
    match /nested/path {	// 'singleSegment' visible in scope.
     allow read; 		// Read rule is evaluated.
    }    
  }
  // Complete match.
  // 'multiSegment' == /hello/nested/path
  match /example/{multiSegment=**} {	
    allow read;			// Read rule is evaluated.
  }
}

위의 예시에서와 같이 path 선언은 다음 변수를 지원합니다.

  • 단일 세그먼트 와일드 카드: 와일드 카드 변수는 중괄호로 변수를 래핑({variable})하여 경로에 선언됩니다. 이 변수는 match문에서 string으로 액세스할 수 있습니다.
  • 재귀 와일드 카드: 재귀적 또는 다중 세그먼트 와일드 카드는 경로나 경로 아래의 다중 경로 세그먼트와 일치합니다. 이 와일드 카드는 설정한 위치 아래의 모든 경로와 일치합니다. 세그먼트 변수의 끝에 =** 문자열을 추가 ({variable=**})하여 이를 선언할 수 있습니다. 이 변수는 match문에서 path 객체로 액세스할 수 있씁니다.

허용

match 블록은 하나 이상의 allow 문을 포함합니다. 다음은 실제 규칙입니다. allow 규칙을 하나 이상의 메서드에 적용할 수 있습니다. Cloud Firestore 또는 Cloud Storage에 수신되는 모든 요청을 허용하려면 allow 문의 조건이 true로 평가되어야 합니다. allow read 같이 조건 없이 allow 문을 작성할 수도 있습니다. 그러나 allow 문에 조건이 포함되어 있지 않으면 메서드의 요청이 항상 허용됩니다.

메소드의 allow 규칙 중 하나라도 충족이 되면 요청이 허용됩니다. 또한 더 광범위한 규칙으로 액세스를 허용하면 규칙이 액세스를 허용하고, 액세스를 제한할 수 있는 더 세부적인 규칙은 모두 무시합니다.

다음 예시에서 모든 사용자가 자신의 파일을 읽거나 삭제할 수 있는 경우를 살펴보세요. 더 세부적인 규칙에 따라 사용자가 자신의 파일에 쓰기를 요청하고 해당 파일이 PNG인 경우에만 쓰기가 허용됩니다. 사용자는 PNG 파일이 아니더라도 하위 경로의 모든 파일을 삭제할 수 있으며, 이는 이전 규칙에서 허용했기 때문입니다.

service firebase.storage {
  // Allow the requestor to read or delete any resource on a path under the user directory.
  match /users/{userId}/{anyUserFile=**} {
    allow read, delete: if request.auth != null && request.auth.uid == userId;
  }
  
  // Allow the requestor to create or update their own images.
  // When 'request.method' == 'delete' this rule and the one matching.
  // any path under the user directory would both match and the 'delete'
  // would be permitted.
  
  match /users/{userId}/images/{imageId} {
    // Whether to permit the request depends on the logical OR of all
    // matched rules. This means that even if this rule did not explicitly
    // allow the 'delete' the earlier rule would have.
    allow write: if request.auth != null && request.auth.uid == userId && imageId.matches('*.png');    
  }  
}

메서드

allow 문에는 동일한 메서드의 수신 요청에 대해 액세스를 허용하는 메서드가 포함되어 있습니다.

편리한 메소드

메서드요청 유형
read모든 유형의 읽기 요청
write모든 유형의 쓰기 요청

표준 메소드

메서드요청 유형
get단일 문서 또는 파일에 대한 읽기 요청
list쿼리 및 컬렉션에 대한 읽기 요청
create새 문서 또는 파일 작성
update기존 데이터베이스 문서에 쓰기 또는 파일 메타데이터 업데이트
delete데이터 삭제

같은 match 블록의 읽기 메서드가 중첩되거나 같은 path 선언의 쓰기 메서드가 중첩되면 안 됩니다.

예를 들어 다음과 같은 규칙은 실패합니다.

service bad.example {
  match /rules/with/overlapping/methods {
    // Thie rule allows read to all authenticated users.
    allow read: if request.auth != null;    
  }
  match another/subpath {
    // This secondary, more specific read rule causes an error.
    allow get: if request != null && request.auth.uid == 'me';
    // Overlapping write methods in the same path cause an error as well
    allow write: if request.auth != null;
    allow create: if requesst.auth != null && request.auth.uid == 'me';    
  }    
}

함수

보안 규칙이 복잡해지며 조건 세트를 함수로 묶어 규칙 세트 전체에서 재사용할 수 있습니다. 보안 규칙에서는 커스텀 함수를 지원합니다. 커스텀 함수의 구문은 자바스크립트와 비슷하지만, 보안 규칙 함수는 도메인 언어로 작성되며 다음과 같은 중요한 제한사항이 있습니다.

  • 함수는 reture문 하나만 포함할 수 있습니다. 다른 로직은 포함할 수 없습니다. 예를 들어 루프를 실행하거나 외부 서비스를 호출할 수 없습니다.
  • 함수는 정의된 범위에 속하는 함수와 변수에 자동으로 액세스할 수 있습니다. 예를 들어 service cloud.firestore 범위 안에 정의된 함수는 resource 변수get(), exists() 등의 기본 제공 함수에 액세스할 수 있습니다.
  • 함수는 다른 함수를 호출할 수 있지만 재귀 호출은 금지됩니다. 호출 스택 깊이는 최대 20으로 제한됩니다.
  • 규칙 버전 v2에서 함수는 let 키워드를 사용하여 변수를 정의할 수 있습니다. 함수는 최대 10개의 let 바인딩을 가질 수 있지만 반환 구문으로 끝나야 합니다.

함수는 function 키워드로 정의되며 0개 이상의 인수를 취합니다. 예를 들어 위 예시에 사용한 두 가지 유형의 조건을 하나의 함수로 결합할 수 있습니다.

service cloud.firestore {
  match /databases/{database}/documents {
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';      
    }    
    match /cities/{city} {
      allow read, write: if signedInOrPublic();      
    }    
    match /users/{user} {
      allow read, write: if signedInOrPublic();      
    }
  }    
}

다음은 함수 인수let 할당을 보여주는 예시입니다. let 할당 문은 세미콜론으로 구분해야 합니다.

function isAuthorOrAdmin(userId, article) {
  let isAuthor = article.author == userId;
  let isAdmin = exists(/databases/${database}/documents/admins/${userId});
return isAuthor || isAdmin;  
}

isAdmin 할당이 관리자 컬렉션 조회를 시행하는 방법에 유의하세요. 불필요한 조회가 필요 없는 지연 평가의 경우 isAuthor가 true(&& 비교의 경우) 또는 false(|| 비교의 경우)로 표시되는 경우에만 &&(AND) 및 ||(OR) 비교의 단락 특성을 활용하여 두번째 함수를 호출합니다.

function isAdmin(userId) {
  return exists(/databases/${database}/documents/admins/${userId});
}
function isAuthorOrAdmin(userId, article) {
  let isAuthor = article.author == userId;
  // '||' is short-circuiting; isAdmin called only if isAuthor == false.
  return isAuthor || isAdmin(userId);
}

보안 규칙에서 함수를 사용하면 규칙이 복잡해져도 쉽게 관리할 수 있습니다.

조건 작성

request 변수: request 변수에는 다음 필드와 해당하는 정보가 포함됩니다.

  • request.auth
    Firebase 인증의 사용자 인증 정보를 포함하는 JSON 웹 토큰(JWT)입니다. auth 토큰에는 Firebase 인증을 통해 생성한 표준 클레임 세트와 모든 커스텀 클레임이 포함됩니다. Firebase 보안 규칙 및 인증을 자세히 알아보세요.
  • request.method
    request.method는 표준 메서드 또는 커스텀 메서드일 수 있습니다. 또한 모든 읽기 전용 또는 모든 쓰기 전용 표준 메서드 각각에 전용되는 규칙의 작성을 단순화하는 데 사용할 수 있는 편리한 메서드인 read와 write도 있습니다.
  • request.params
    request.params에는 평가에 유용할 수도 있는 request.resource와 특별히 관련되지 않은 모든 데이터가 포함됩니다. 실제로 이 매핑은 모든 표준 메서드의 경우 비어 있어야 하며, 커스텀 메서드의 경우 리소스 이외의 데이터를 포함해야 합니다. 서비스는 매개변수로 표시된 모든 키와 값의 유형을 수정하거나 이름을 바꾸지 않도록 주의해야 합니다.
  • request.path
    request.path는 대상 resource의 경로입니다. 이 경로는 서비스를 기준으로 합니다. /와 같이 url이 아닌 안전한 문자를 포함하는 경로 세그먼트는 url로 인코딩됩니다.
  • resource 변수
    resource는 키-값 쌍의 맵으로 표현되는 서비스 내 현재 값입니다. 조건 내에서 resource를 참조하면 서비스에서 읽은 값은 최대 하나가 됩니다. 이 조회는 리소스의 모든 서비스 관련 할당량을 계산합니다. get 요청의 경우 resource는 거부 시 할당량만 계산합니다.

연산자 및 연산자 우선순위

아래 표를 통해 Cloud Firestore 및 Cloud Storage의 규칙에서 연산자 우선순위를 참조할 수 있습니다.

ab, 필드 f, 색인 i는 임의로 주어진 표현식입니다.

연산자설명결합
a[i] a() a.f색인, 호출, 필드 액세스왼쪽에서 오른쪽으로
!a -a단항 부정오른쪽에서 왼쪽으로
a/b a%b a*b곱셈 연산자왼쪽에서 오른쪽으로
a+b a-b덧셈 연산자왼쪽에서 오른쪽으로
a>b a>=b a<=b관계 연산자왼쪽에서 오른쪽으로
a in b목록 또는 맵에 존재왼쪽에서 오른쪽으로
a is type유형 비교: type은 bool, int, float, number, string, list, map, timestamp, duration, path, latlng일 수 있습니다.왼쪽에서 오른쪽으로
a==b a!=b비교 연산자왼쪽에서 오른쪽으로
a && b조건부 AND왼쪽에서 오른쪽으로
ab
a ? true_value : false_value3항 표현식왼쪽에서 오른쪽으로

0개의 댓글