이번 글은 지난 포스트에 이어지는 CAS 배포에 대한 글입니다. 스마트 클라이언트 시스템을 구축할 때 항상 걸림돌이 되는 것이 클라이언트 마다 수행되어야 하는 CAS 설정이지요. 대개의 경우 스마트 클라이언트는 필요한 CAS 권한이 부족하기 때문에 클라이언트마다 사이트 혹은 URL에 대해 CAS 설정을 해주어야만 합니다. 이러한 CAS 설정 작업을 최종 사용자에게 수작업으로 하도록 한다는 것이 매우 불합리하기 때문에 자동으로 CAS 설정을 해주는 모듈을 만들고 이것이 클라이언트에서 수행되도록 해주는 일련의 CAS 설정 배포 작업이 필요한 겁니다. 이번 포스트는 CAS 설정을 수행하는 코드들을 살펴보고 이것을 어떻게 클라이언트에 배포하고 수행하도록 하는지 살펴보도록 하겠습니다.
필자주) 이 글에 나타나는 MSDN에 대한 링크는 가능한 한글 자료를 링크하려고 애썼습니다만...(한글 자료 찾는 거 졸라 귀찮습니다...) 일부 자료는 아직 한글화 되어 있지 않아 부득이 영문 자료가 링크된 경우도 있으니 양지하시길 바랍니다. 한글 문서가 오히려 이해하는데 더 어렵다고 생각하시는 분은 브라우저 주소창의 URL에서 ko-KR 을 en-US로 바꾸시면 영문 자료에 접근하실 수 있습니다.
시리즈 목차
Smart Client (VII) : CAS Deployment
지난 포스트에서는 CAS(Code Access Security; 다시 말하지만 맥주 이름이 아니다... -_-)의 기본에 대해 살펴보았다. 사실 지난 포스트에서 알아본 내용은 CAS 프로그래밍에 대해서는 거의 다루지 않았다. 즉, CAS 보안을 통해 자원(파일, 네트워크 등등)을 보호하는 코딩 방법보다는 닷넷 프레임워크에 기본으로 설정된 CAS 보안 정책이 어떻게 구성되어 있는지를 주로 살펴보았다는 말이다. 그 이유는 간단하다. 사실 스마트 클라이언트 프로그램을 작성하다 보면 대부분의 경우에서 CAS 보안을 통해 자원을 보호하는 것 보다 기본 CAS 보안을 어기지 않도록 CAS 설정을 잡아주는 일이 빈번하게 발생하기 때문이다.
그래서 이번 포스트는 프로그래밍적으로 CAS 보안 정책을 수정하는 방법과 이것을 스마트 클라이언트 시나리오 상에서 어떻게 배포하는지에 대해서 알아보도록 하겠다. CAS 정책을 관리하는 클래스와 메쏘드는 상당히 많다. 쑛도 모르는 필자가 어찌 이들을 모두 설명하겠는가? CAS 보안 정책에 코드 그룹을 추가하고 권한을 주는 부분만을 중점적으로 살펴보도록 할 터이니 독자들을 그렇게 알고 있으면 좋겠다. 궁금한 게 더 많다고? 고수가 될 가능성이 보이는 아주 좋은 징조다... MSDN을 찾아 보면, 각 클래스와 메쏘드의 구체적인 설명과 예제 코드도 있으니 시도해보기 바란다.
Programmatically CAS Policy Management
수작업이 아닌 코드에 의해 CAS 보안 정책을 바꾸는 작업은 스마트 클라이언트 시나리오에서 매우 중요한 미션이다. 지난 포스트에서 이미 살펴 보았지만, 코드 그룹을 추가하고 권한을 주는 작업은 아주 간단한 작업이 아닐 뿐더러 설정을 잘못하면 스마트 클라이언트 자체가 구동되지 않는 현상을 초래할 수도 있다. 최종 사용자에게 이러한 CAS 설정을 하도록 한다면 모르긴 몰라도 그 개발자 혹은 아키텍트는 아마 배터지게 욕을 쳐먹거나 심한 경우 쫓겨날 각오를 해야 할지도 모른다. 설령 그렇게 한다 할지라도, 설정에 관여된 문의 전화를 매일 수십 통씩 받아야 할 것이다. 이보다 더 간단한 설정조차 스스로 할 줄 모르는 사용자는 부지기수이다.
따라서, 프로그램적으로 이러한 CAS 보안 정책을 추가/수정하고 이 코드를 클라이언트에 배포하고 수행하도록 하는 것이 정신/육체 건강에 매우 도움이 될 것임을 확신한다. 그래서 프로그램 코드에 의해 CAS 보안 정책을 수정하는 방법은 존재하는가? 친절한 닷넷씨를 무시하지 마라. .NET Framework Configuration MMC에서 수행하는 거의 모든 작업은 관련 클래스와 메쏘드를 모두 가지고 있다. 오늘 집중적으로 살펴볼 부분은 System.Security 네임스페이스와 System.Security.Policy 네임스페이스에 존재하는 일련의 클래스들 이다.
Listing Code Groups
System.Security 네임스페이스와 System.Security.Policy 네임스페이스는 CAS 보안 정책을 다루는 다양한 클래스를 포함하고 있다. 프로그램적으로 CAS 보안 정책을 변경하고 싶다면 바로 이들 네임스페이스의 클래스들을 사용해야만 하는 것이다. 그 중 CAS 정책을 관리하기 위해 시작점이자 종료점이 되는 클래스가 SecurityManager 클래스 이다. 이 클래스에는 CAS 보안이 활성화 되어 있는지를 검사하는 SecurityEnabled 속성부터 CAS에서 가장 기본이 되는 권한인 코드 수행 권한(Execution Rights)이 있는지를 검사하는 CheckExecutionRight 속성이 포함되어 있으며, 특정 권한(Permission)이 현재 코드 및 호출 스택상의 상위 코드들(어셈블리들)에 존재하는지를 검사하는 IsGranted 메쏘드, 주어진 어셈블리의 증명 정보(evidence)를 이용하여 CAS 정책을 적용한 결과 권한 집합을 구하는 ResolovePolicy 메쏘드, XML 형태의 CAS 정책 파일을 로드 하는 LoadPolicyLevelFromFile, 변경된 CAS 정책을 저장하는 SavePolicy 메쏘드, CAS 정책 수준(Policy Level)들을 반환하는 PolicyHierarchy 메쏘드 등을 가지고 있다.
다른 메쏘드들은 MSDN 라이브러리에서 그 용도를 추측하거나 예제 코드를 통해 충분히 이해를 높일 수 있다. 여기서는 SecurityManager 클래스를 통해 CAS 정책 수준들(Enterprise, Machine, User 수준)을 읽고 그 중 Machine 정책 수준을 알아내는 예제 코드를 보인다.
// Policy 레벨 열거를 얻는다.
IEnumerator enumerator = SecurityManager.PolicyHierarchy();
// Policy 레벨들(Enterprise, Machine, User) 중에서 Machine Policy를 찾는다.
PolicyLevel machineLevel = null;
while (enumerator.MoveNext()) {
PolicyLevel level = (PolicyLevel)enumerator.Current;
if (level.Label == "Machine") {
machineLevel = level;
}
}
if (machineLevel == null)
throw new InvalidOperationException("Machine Policy를 찾을 수 없습니다.");
리스트1. Machine 정책 수준을 찾는 예제 코드
리스트1은 CAS 보안 정책 수준에서 Machine 정책 수준을 찾는 코드이다. 먼저 SecurityManager 클래스의 PolicyHierarchy 메쏘드를 호출하여 정책 수준들에 대한 IEnumerator 인터페이스를 얻는다. 달랑 IEnumerator 만을 반환하는 것이 이상하지만 이 인터페이스를 통해 각 정책 수준을 나타내는 PolicyLevel 클래스들을 알아 낼 수 있다. PolicyLevel 클래스는 CAS 정책 수준을 나타내는 클래스로서 각 CAS 정책 수준에 포함되어 있는 코드 그룹(Code Group) 및 권한 집합(Permission Set) 등에 대한 정보를 알아 낼 수 있다.
정책 수준을 나타내는 PolicyLevel 클래스를 얻었으니, 이제 코드 그룹에 액세스할 수 있다. PolicyLevel 클래스의 RootCodeGroup 속성은 정책 수준의 최상위 코드 그룹(All_Code 란 이름을 가진 코드 그룹이다)을 반환한다. 이 속성이 반환하는 타입은 CodeGroup 클래스 타입을 반환하는데, CAS 정책의 코드 그룹을 나타내는 클래스라는 점은 찍어도 맞출 수 있을 것이다. CodeGroup 클래스는 abstract 클래스로서 FileCodeGroup, NetCodeGroup, UnionCodeGroup 등의 실제 클래스들에 대한 베이스 클래스이다. 실제 클래스가 어떤 클래스이건 CodeGroup에서 파생되었으므로 CodeGroup 클래스를 통해 액세스하는 것이 불필요한 캐스팅을 피하고 InvalidCastException을 피하는 지름길이다.
코드 그룹은 지난 포스트에서 설명했다시피 계층 구조를 갖는다. 코드 그룹은 하위 코드 그룹을 가질 수 있는 것이다. CodeGroup 클래스 역시 하위 코드 그룹을 나타내기 위해 Children 속성을 갖는다. 이 속성은 하위 코드 그룹을 열거하기 위해 IList 인터페이스를 반환한다. CodeGroup 클래스와 Children 속성을 이용하여 재귀 호출 방식으로 코드를 작성하면 주어진 정책 수준의 코드 그룹들을 나열할 수 있다.
// Root code group을 구한다.
CodeGroup rootCodeGroup = machineLevel.RootCodeGroup;
// 루트 코드 그룹의 하위 코드 그룹들의 이름과 설정된 권한 집합을 표시한다.
foreach (CodeGroup group in rootCodeGroup.Children) {
Console.WriteLine("Code Group Name = {0}, PermissionSet Name={1}",
group.Name, group.PermissionSetName);
}
리스트2. 정책 수준의 최상위 코드 그룹과 그 하위 코드 그룹을 열거하는 예제 코드
리스트2는 정책 수준의 최상위 코드 그룹을 알아내고 그 최상위 코드 그룹의 하위 코드 그룹들을 표시하는 예제 코드이다. 간단한 예제이므로(핑계가 좋다. 사실은 귀찮아서... -_-;) 굳이 재귀 호출까지 사용하여 모든 코드 그룹 표시하는 만행을 저지르지는 않았다. -_-;
Adding a Code Group
개략적으로 코드 그룹을 열거하는 방법을 알아 봤으니 이제 코드 그룹을 추가하는 방법 역시 알아 보도록 하자. 대략 경험 깨나 해본 개발자라면 어렵지 않게 방법을 유추해 낼 수 있을 것이다. PolicyLevel 클래스가 RootCodeGroup 속성을 통해 최상위 코드 그룹을 반환하므로 이 코드 그룹에 새롭게 만든 코드 그룹을 추가하면 되지 않을까? 그대의 통밥은 대단하다. 그렇다. 추가하고자 하는 새로운 코드 그룹을 만들고 그 코드 그룹을 부모 코드 그룹에 추가하면 되는 것이다.
새로운 코드 그룹을 생성하기 위해서는 CodeGroup 객체를 만들면 된다. CodeGroup 클래스는 추상 클래스 이므로 직접 생성이 곤란하다. 대신 UnionCodeGroup 클래스를 사용하면 된다. 이 클래스는 다른 코드 그룹 클래스들에 비해 가장 많이 사용되는 클래스이다. .NET Framework Configuration MMC에서 코드 그룹을 추가하면 바로 UnionCodeGroup이 추가된다. 사실 필자도 다른 코드 그룹은 사용해 본적도 없다. UnionCodeGroup은 자기 자신 뿐만 아니라 자식들의 코드 그룹까지도 권한 계산을 포함하도록 해준다(이는 지난 포스트의 권한 계산에서 이미 설명한 바 있다).
코드 그룹을 생성하기 위해서는 먼저 이 코드 그룹이 어떤 멤버십 조건(membership condition)을 사용하는지 명시해야만 한다. 멤버십 조건은 ZoneMembershipCondition 클래스, SiteMemebrshipCondition 클래스, UrlMemebrshipCondition 클래스, AllMembershipCondition 클래스 등이 존재한다. 스마트 클라이언트 시나리오에서 어떤 멤버십 조건을 사용할 것인가가 결정되었다면 앞서 열거한 멤버십 조건 클래스들 중 하나를 선택하여 멤버십 조건을 생성하면 된다. 이들 클래스는 AllMembershipCondition을 제외하고 멤버십의 조건으로 인터넷 영역(zone), 사이트 이름, URL 중 하나를 매개변수로 취할 수 있다.
멤버십 조건을 생성했다면, 이제 코드 그룹이 가질 권한 집합을 명시하면 된다. 지난 포스트에서는 단순히 권한 집합이란 용어만을 사용했지만 권한 집합은 다시 이름을 갖는 명명된 권한 집합(Named Permission Set)과 그렇지 않은 권한 집합으로 나누어 볼 수 있다. .NET Framework Configuration MMC에서 권한 집합을 정의하면 이는 명명된 권한 집합을 정의하는 것이다. 명명된 권한 집합은 NamedPermissionSet 이란 클래스에 의해 표현된다. 이 클래스의 생성자에 이미 정의된 권한 집합(FullTrust, Internet, LocalInternet 등등의 이미 정의된 권한 집합들)의 이름을 주면 해당 권한 집합을 설정할 수 있다. 반면, 명명되지 않은 권한 집합은 PermissionSet 클래스(한글 MSDN 문서가 아직 준비 되지 않았단다...)를 통해 정의되며, 이 클래스의 인스턴스를 생성하고, FileIOPermission, SocketPermission 등의 구체적인 권한 클래스의 인스턴스를 생성하여 생성한 Permission 클래스의 인스턴스에 추가(AddPermission 메쏘드)해야만 한다.
말로만 하면 당췌 이해를 못하는 인간들이 많으니 예제 코드 서비스 들어간다.
// URL 멤버쉽 생성
UrlMembershipCondition membership = new UrlMembershipCondition("http://localhost.com/SmartClientBasic/*");
// 권한 집합 생성 (full trust)
NamedPermissionSet permissionSet = new NamedPermissionSet("FullTrust");
PolicyStatement policyStatement = new PolicyStatement(permissionSet);
// 추가할 코드 그룹 생성
UnionCodeGroup cg = new UnionCodeGroup(membership, policyStatement);
cg.Name = "테스트 코드 그룹";
cg.Description = "테스트용 코드 그룹임. 졸라 재미남...";
rootCodeGroup.AddChild(cg);
리스트3. URL 멤버십 조건을 사용하는 코드 그룹을 생성하고 설정하여 추가하는 예제 코드
리스트3은 코드 그룹을 생성하고 최상위 코드 그룹(All_Code)에 추가하는 예제 코드 이다. 앞서 리스트1과 리스트2에서처럼 CAS 정책 수준을 얻고 최상위 코드 그룹을 이미 알아 냈다고 가정하자. 먼저 UrlMembershipCondition 클래스를 통해 URL 멤버십 조건을 생성했다. 물론 멤버십 조건을 http://localhost.com/SmartClientBasic/* 으로 했다. 이제 NamedPermissionSet 클래스를 통해 FullTrust 권한 집합을 생성했다. 그리고 코드 그룹을 생성할 때, 만들어 놓은 멤버십과 권한 집합을 설정하였으며, 적절한 코드 그룹과 이름을 주었다. 마지막으로 최상위 코드 그룹에 대해 AddChild 메쏘드를 호출함으로써 코드 그룹을 추가 하였다.
리스트3에서 등장한 PolicyStatement 클래스는 코드 그룹이 갖는 권한 집합과 속성을 표시하는 클래스이다. 권한 집합까지는 알겠는데 코드 그룹의 속성이라니? 에이 C8 쑛같이 복잡하기도 하다... 지난 포스트에서 밝혔어야 했으나 글이 너무 길어지고 너무 복잡해 지는 것을 피하기 위해 언급하지 않았었는데, 지금 간략히 언급하기로 한다. 코드 그룹은 Exclusive 속성과 LevelFinal 속성을 가질 수 있다. 대략 설명하기 귀찮으니 MSDN 자료와 PolicyStatement 클래스의 Attribute 속성를 살펴보기 바란다. 지금도 필자는 너무 복잡타. 어찌 되었건 PolicyStatement 를 코드 그룹에 명시함으로써 코드 그룹의 생성이 완료되는 것이다.
Saving Policy
코드 그룹 추가가 끝났다면 이제 CAS 정책을 저장해야 한다. 저장하지 않은 CAS 정책은 현재 프로세스에 로드 된 닷넷 런타임에 의해서만 적용되는 코드 그룹이 되고 만다. 따라서 저장을 수행하여 영구적인 설정이 되도록 해야만 하며, 저장을 해야만 .NET Framework Configuration MMC에서 설정사항을 볼 수 있다. 저장된 CAS 정책은 닷넷 런타임이 새로이 시작될 때마다 읽혀지고 적용될 것이다.
저장하는 방법은 대단히 간단하다. SecurityManager 클래스의 SavePolicy 메쏘드를 달랑 호출하면 끝이다.
// CAS 설정 저장
SecurityManager.SavePolicy();
Sample - Creating Code Group
지금까지 설명한 것을 모두 모아, URL 멤버십 조건을 사용하며 FullTrust 권한을 갖는 코드 그룹을 Machine 정책 수준에 추가하는 예제 코드를 작성해 보자. 뭐 어려울 것도 없다. 앞서 보였던 예제 코드들을 적절히 조합하면 땡 이다. 리스트4의 AddUrlCodeGroup 메쏘드가 바로 그것이다. AddUrlCodeGroup 메쏘드는 URL 멤버십 조건을 사용하여 코드 그룹을 추가해 준다. 코드의 내용은 이미 앞서 대부분 설명한 내용이므로 더 이상 설명하지 않겠다. 사용방법 역시 졸라 쉽다. 고로 설명은 패스~~~ (아... 귀차니즘이여...)
public static bool AddUrlCodeGroup(string url, string name, string description)
{
// Policy 레벨 열거를 얻는다.
IEnumerator enumerator = SecurityManager.PolicyHierarchy();
// Policy 레벨들(Enterprise, Machine, User) 중에서 Machine Policy를 찾는다.
PolicyLevel machineLevel = null;
while (enumerator.MoveNext()) {
PolicyLevel level = (PolicyLevel)enumerator.Current;
if (level.Label == "Machine") {
machineLevel = level;
}
}
if (machineLevel == null)
throw new InvalidOperationException("Machine Policy를 찾을 수 없습니다.");
// Root code group을 구한다.
CodeGroup rootCodeGroup = machineLevel.RootCodeGroup;
// 코드 그룹이 이미 존재하는가 확인한다.
foreach (CodeGroup group in rootCodeGroup.Children) {
if (group.MembershipCondition is UrlMembershipCondition) {
if (group.Name == name) {
return false;
}
}
}
// URL 멤버쉽 생성
UrlMembershipCondition membership = new UrlMembershipCondition(url);
// 권한 집합 생성 (full trust)
NamedPermissionSet permissionSet = new NamedPermissionSet("FullTrust");
PolicyStatement policyStatement = new PolicyStatement(permissionSet);
// 추가할 코드 그룹 생성
UnionCodeGroup cg = new UnionCodeGroup(membership, policyStatement);
cg.Name = name;
cg.Description = description;
rootCodeGroup.AddChild(cg);
// CAS 설정 저장
SecurityManager.SavePolicy();
return true;
}
리스트4. URL 멤버십을 사용하는 코드 그룹을 추가하는 유틸리티 메쏘드
리스트4에서 눈 여겨 볼 부분은 코드 그룹이 이미 존재하는가를 확인하는 부분이다. 루트 코드 그룹의 자식들에 대해 루프를 돌면서 매개변수로 주어진 코드 그룹의 이름이 존재하는가 확인하고, 만약 존재한다면 아무런 작업도 하지 않도록 한 것이다. 뭐 어려울 것이 없으므로 더 이상 설명은 필요하지 않으리라 본다. 위 코드가 너무 어렵다고 생각된다면 지금 당장 다른 직종으로 옮기는 것을 고려해 보라고 충고하고 싶다.
위 코드가 다 잘 이해 된다면, URL 멤버십이 아닌 사이트 멤버십을 사용하는 AddSiteCodeGroup 메쏘드도 만들어보길 권장한다. 이 역시 허벌나게 쉽다. UrlMembershipCondition 클래스 대신 SiteMembershipCondition 클래스를 사용하기만 하면 된다. 닭대가리가 아닌 다음에야 못할 이유가 없다고 본다. 꼬끼오~~~
Deployment of CAS Setting
지금까지 프로그램적으로 코드에 의해 CAS 정책을 수정하는 방법에 대해 노가리를 풀어봤다. 보다 중요한 것은 리스트4와 같은 코드를 어떻게 클라이언트까지 자동(?)으로 배포하고 수행시키냐는 것이다. 이제부터 CAS 정책을 배포하는데 필요한 몇 가지 권고 사항과 배포 방법을 살펴보도록 하겠다. 다양한 방법이 있을 수 있겠지만 여기서는 각 방법에 대한 상세한 설명은 생략하겠다. 왜냐고? 음... 그건... 필자가 이런 것을 컨설팅 해주는 것을 업으로 삼고 있는지라... 필자도 좀 먹고 살아야 할 거 아닌가? -_-; 그래도 권장되는 한가지 방법에 대해서는 좀 더 자세히 노가리를 풀어볼까 한다.
Selecting Membership Condition
먼저 배포할 CAS 정책에서 스마트 클라이언트가 포함될 코드 그룹이 어떤 멤버십 조건을 사용할지 결정해야 한다. 무식하게 기존 Internet 코드 그룹에 권한을 주는 짓을 하지 않을 것이라면 어플리케이션을 다운로드 할 URL 혹은 사이트로서 코드 그룹을 지정해 주는 것이 일반적이다. 혹은, 모든 DLL에 서명을 하고 StrongName 멤버십을 사용하는 방법도 생각해 볼 수 있지만 그다지 유연성이 높지는 않다. 왜냐면 다음 포스트에서 설명할 DLL이 다운로드 되는 것과 StrongName이 관련이 있을 뿐더러, 항상 동일한 .snk 파일을 사용해야 하는 등의 제약이 있기 때문이다. 쉽게 말해 StrongName 은 비추라고 할 수 있겠다.
보안 위협을 가장 적게 노출시키는 멤버십 조건은 URL 멤버십이다. 스마트 클라이언트 어셈블리(DLL, EXE)를 다운로드 할 URL에 대해 권한을 주도록 코드 그룹을 추가하는 것으로서 이 외의 URL은 기존의 Internet_Zone 혹은 LocalIntranet_Zone 코드 그룹에 속하게 되므로 권한에 제약을 받게 된다. 예로서 http://www.simpleisbest.net/Apps/SmartClientBasic/* 와 같은 URL을 사용하여 코드 그룹을 만들면 되겠다. URL 멤버십의 단점은 여러 디렉터리에서 어셈블리를 다운로드 하는 경우, 코드 그룹을 여러 개 추가하거나 보다 상위 디렉터리를 기준으로 URL을 주어야 한다는 점이다.
이외에 생각해 볼 수 있는 코드 그룹 멤버십은 사이트 멤버십이다. 사이트를 지정하고 그 사이트에서 다운로드 하는 어셈블리들에게 보다 완화된 권한을 주는 것이다. 사이트 멤버십을 사용하면 좋은 경우는, 필자가 관여된 P 건설 프로젝트처럼 대형 프로젝트에서 여러 웹 서버가 서브 시스템으로서 사용되는 경우일 것이다. 이 프로젝트는 5개의 서브 시스템 모두 스마트 클라이언트를 사용했었는데, 서브 시스템마다 서버의 DNS 이름이 달랐다. URL 멤버십을 사용한다면 최소 5개의 코드 그룹이 필요할 것이다. 이 때 사이트 멤버십을 사용하면 코드 그룹 1개만을 추가할 수도 있다. 사이트 이름 역시 와일드 카드(*)를 사용할 수 있으므로 *.domain.com 과 같이 사이트를 지정하면 server1.domain.com, server2.doamin.com 등 domain.com 으로 끝나는 모든 사이트들에 대해 CAS 권한 설정이 가능하다.
Use FullTrust
추가할 코드 그룹의 멤버십 조건을 결정했다면, 코드 그룹에 어떤 권한을 줄지 생각해 봐야 한다. 결론부터 말하면, 필자는 눈 딱감고 FullTrust를 사용하라고 권장하고 싶다. 물론 보안을 고려한다면 권한은 적게 줄 수록 좋을 것이다. 만약 인트라넷 시스템이라면(스마트 클라이언트를 고려한다면 아직까진 99.99%가 인트라넷 시스템을 위한 것일 게다), URL 멤버십과 사이트 멤버십은 커다란 보안 구멍을 의미하지 않는다. 게다가 일부 닷넷의 기능들(대표적으로 P/Invoke, Reflection 등)은 FullTrust가 아니면 작동조차 하지 않기 때문에 SecurityException 발생을 피할 수 있는 가장 편리한 방법이 FullTrust를 사용하는 것이다.
FullTrust를 사용하지 않으면, 필요한 권한들 만을 묶어서 새로운 권한 집합을 만들어 주어야 하는데, 이것 역시 만만한 작업이 아니기에(이 포스트에서는 설명하지 않았다) 더욱 FullTrust를 권장하는 바이다. 본인이 매우 용감하며, 보안 허점을 추호도 용서하지 못하겠다는 정의감에 불탄다고 생각된다면, MSDN을 좀더 뒤져서 리스트4에 권한 집합을 생성하는 코드를 추가해야 할 것이다.
Packaging CAS Policy
코드 그룹과 권한을 결정했으면, 이것을 클라이언트로 배포하기 위한 패키지를 만들어야 한다. 몇 가지 방법이 있을 수 있다. 첫째로 MSI(Microsoft Installer) 패키지로 만들어 설치하는 방법이다. 작성법은 간단하다. 먼저 리스트4와 같은 코드를 포함하는 DLL을 작성한다. 그리고 이 DLL에 Installer 클래스를 추가한다(Installer 클래스에 대해서는 구질구질하게 여기서 설명하지 않겠다). 이 Installer 클래스의 생성자 혹은 Install 메쏘드의 오버라이드에서 리스트4의 AddUrlCodeGroup을 호출하도록 한다. 필요하다면 URL, 코드 그룹의 이름 등을 하드 코드 하는 것도 주저하지 말자. DLL 작성이 완료되면 installutil.exe를 이용하여 테스트를 수행해 본다.
이제 .MSI 패키지를 작성하기 위해 비주얼 스튜디오에서 설치 프로젝트를 만든다. 설치 프로젝트의 Custom Action 에 앞서 작성한 DLL을 포함시킨다. 이렇게 하면, MSI 패키지에 의해 설치 최종 단계에서 Custom Action 에 등록된 DLL 들이 installutil.exe을 통해 수행되게 되므로 CAS 설정을 배포할 수 있는 것이다. 작성한 MSI 패키지를 웹 서버에 올려놓아 최종 사용자들이 다운로드 받아 MSI를 설치하도록 유도하는 웹 페이지를 만들면 된다. 간단하지 않은가?
두 번째 방법은 CAS 설정을 수행하는 닷넷 어플리케이션 EXE를 만드는 것이다. 리스트4를 참조하여 CAS 설정을 수행하는 exe를 만들고 이것을 압축하여 웹 서버에 올려놓고 최종 사용자들이 다운로드하고 수행하도록 유도한다. 이때 반드시 압축해야 한다. 그렇게 하지 않으면 작성한 exe를 독립 스마트 클라이언트로 인식해 버릴 것이며 독립 스마트 클라이언트는 CAS 보안 검사에 의해 리스트4와 같은 코드를 수행할 권한이 없다!
세 번째 방법은 ActiveX를 사용하는 것이다. 먼저 리스트4와 같은 코드를 포함하는 닷넷 DLL을 만든다. 그리고 C++를 이용하여 간단한 ActiveX 컨트롤을 만들고(UI는 없는 ActiveX 일 것이다), 이 ActiveX 컨트롤에서 닷넷 엔진을 호스팅하여 작성해 놓은 닷넷 DLL을 호출하도록 작성한다. 이제 ActiveX DLL과 닷넷 DLL을 하나의 CAB 파일로 만들고 웹 페이지에 끼워 넣으면 자동으로 CAS 설정이 배포될 것이다. 말은 쉽지만 웬만한 COM 및 닷넷 지식으로는 엄두도 내기 힘든 기법이다. 사용자가 수동으로 설치하는 액션이 필요 없다는 장점이 있지만 구현의 난이도가 매우 높다.
일반적으로 무리 없이 적용 가능한 방법은 첫 번째로 제시한 MSI를 이용하는 방법이다. 왜 그런고 이제 설명하도록 하겠다. 첫째 이유로, 대부분의 클라이언트 PC는 닷넷 프레임워크가 설치되어 있지 않다. 따라서 닷넷 프레임워크를 설치하는 일련의 설치 작업이 거의 99% 이상 필요하기 때문이다. 이런 이유로 닷넷 프레임워크를 설치하는 MSI 패키지를 만들면서 슬쩍 CAS 설정을 수행하도록 하면 되는 것이다.
두 번째 이유로 대부분의 어플리케이션은 고정 불변의 DLL 들을 포함하곤 한다. 이들 DLL의 대표적인 예는 오피스에 대한 Interop DLL, 상용 컨트롤에 대한 DLL 들이다. 이렇게 고정 불변의 DLL들을 스마트 클라이언트 시나리오에 의해 배포하는 것보다는 최초의 1회 설치로서 클라이언트에 배포해 버리는 것이 관리적으로도 편리할 뿐 더러, 어플리케이션을 최초로 사용하는 사용자들에 대해 응답속도를 올릴 수 있는 팁이 된다. 따라서 설치 패키지를 만드는 것이 상당히 자연스러운 작업이 될 것이며, 이 때 CAS 설정을 같이 해주자는 것이다.
세 번째 이유로서 스마트 클라이언트 시나리오로 배포되지 않는 Unmanaged DLL 들 역시 클라이언트에 배포해야 할 상황은 자주 발생하곤 한다(크리스탈 리포트는 managed DLL과 unmanaged DLL로 구성되어 있다). 스마트 클라이언트에서 상용 ActiveX 컨트롤을 사용한다면 이 ActiveX 컨트롤을 배포해야 할 것 아닌가? 그렇다면 당연히 배포 패키지를 필요로 할 것이다.
이러한 이유로서 초기 1회용 배포 패키지를 만드는 것은 전혀 이상한 작업이 아니다. 앞서 말한 것처럼 고정 불변의 닷넷 DLL과 스마트 클라이언트 시나리오로 배포되지 않는 unmanaged DLL들을 배포하는 MSI 패키지를 만들면서 Custom Action에서 CAS 설정을 하도록 MSI를 작성한다. 그리고 이 MSI에 닷넷 프레임워크를 선행적으로 설치하도록 해주는 부트스트래퍼를 설정한다. 이 부트스트래퍼는 닷넷 어플리케이션을 설치하기 전에 닷넷 프레임워크가 설치되어 있는지 검사하고, 설치 되어 있지 않다면 닷넷 프레임워크를 먼저 설치해 주는 역할을 하므로 닷넷 프레임워크가 설치되지 않은 사용자들을 위해서도 안성맞춤이 될 것이다. 부스트래퍼에 대한 상세한 내용은 다음 MSDN 자료를 참고하기 바란다.
닷넷 프레임워크와 고정 DLL 및 unmanaged DLL 그리고 CAS 설정을 포함하는 최종 MSI 패키지를 만들었다면 아마도 이것이 여러 파일들(setup.exe, dotnetfx.exe, *.MSI 등)로 구성되어 있을 것이므로 알집, WinRAR 등의 유틸리티를 통해 단일 압축 exe로 작성하여 웹 서버에 올려놓으면 된다. 사용자들은 단순히 이 exe를 다운로드 받아 수행하면 된다. 이렇게 함으로써, 클라이언트 PC에 닷넷 프레임워크는 물론이요, CAS 설정까지 유도할 수 있으므로 일석 이조의 효과를 낳게 된다.
지금까지 설명한 배포 방법을 보다 상세하게 설명할 수 없음을 독자들은 이해해 주기 바란다. 그 내용이 간단하지 않기 때문에 여기서 구체적으로 예를 들어 설명할 수 없기 때문이다. 물론 필자의 귀차니즘 역시 한 몫 단단히 했음은 두말할 것도 없다. 이 정도를 설명한 것도 필자의 상당한 노하우이므로 이 정도로 만족해 주기 바란다. 그리고 직접 함 시도해 보기를 강력히 권장하는 바이다. 누누이 강조하지만 눈 도둑만으로는 전혀 실력이 향상되지 않는다 !!!
What's Next
이번 포스트에서는 프로그램적으로 CAS 정책을 변경하는 방법에 대해 살펴보았고 구체적인 예제 코드 역시 제공하였다. 그리고 CAS 정책을 배포하기 위한 몇 가지 권고 사항과 배포 방법을 살펴보았다. 점점 포스트가 날림이 되는 것 같아서 영 쮭쮭하지만 그래도 질질 끌리는 것보다는 나을 것 같아서 잽싸게 글을 마무리했다. 불만있으면 필자를 찾아 오도록... -_-;
다음 포스트에서는 스마트 클라이언트 시나리오에서 DLL이 다운로드 되는 과정을 살펴보도록 하겠다. 당췌 스마트 클라이언트 시나리오에서 DLL이 다운로드되는 규칙을 알 수 없다고 하소연하거나 스마트 클라이언트에서 최신 버전이 다운로드 되지 않는다고 하소연 하는 사람들은 다음 포스트에 관심을 가져보기 바란다.