28 December 2020
Admin-panel to whitelist IP's for identity server
One of our clients wanted to be sure that only white-listed IP’s can log into Sitecore to manage their site. We solved this by utilizing IIS’s IP Address and Domain Restrictions feature on the Sitecore Identity Server . That way adding or removing an IP only restarts the app-pool for the Identity Server, not the Sitecore instance. Annoyingly this has meant that every so often I have to log into the production server just to add an IP address to IIS, until I made this admin-page that let’s anyone with the developer role in Sitecore do this without all the hassle.
For this page to work, Microsoft.Web.Administration.dll
needs to be available on the site. It’s available from NuGet. Also, as mentioned in the code, the App Pool needs read/write access to all files in %SystemRoot%\System32\inetsrv\config
as well as the web.config
of the site of which the white-listed IP’s need to be managed, i.e. the relevant Identity Server. Finally you need to put the IIS name of the site of interest as the constant siteOfInterest
.
Update 2: I’ve tweaked the admin page to look better on mobile and accept IPv6 as it turns out IIS actually accepts IPv6 addresses on the back-end, the UI was just never updated for this.
Update 1: As I was deploying this I ran into another setting that needs tweaking. IIS needs to allow write delegation on the IP Address and Domain Restrictions feature for the relevant (Identity Server) site. Go to the root of IIS and open Feature Delegation :
Then pick Custom Site Delegation on the right (unless you wanna set this for all sites which I would advice against)
Then select the Sitecore Identity Server up top, select IP Address and Domain Restrictions on the bottom and check if it says Read/Write . If not, click Read/Write on the right Actions panel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<%@ Page Language="C#" AutoEventWireup="true" Debug="true" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Linq" %>
<%@ Import Namespace="System.Data.Linq" %>
<%@ Import Namespace="Microsoft.Web.Administration" %>
<%@ Import Namespace="Sitecore" %>
<script runat="server" language="c#">
// For this administration page to work, IIS needs to be configured to allow Read/Write
// delegation of the IP Address and Domain Restrictions feature on the Site of Interest
// (overrideMode="Allow" for system.webServer/security/ipSecurity)
// And the user this page runs under (App Pool) must have read/write access to both:
// - %SystemRoot%\System32\inetsrv\config of the webserver
// - web.config of the Site of Interest
private const string siteOfInterest = "NAME OF YOUR IIS SITE";
private ServerManager serverManager;
private ServerManager ServerManager
{
get
{
return serverManager ?? (serverManager = new ServerManager());
}
}
private Microsoft.Web.Administration.ConfigurationElementCollection IpCollection
{
get
{
return ServerManager.Sites
.FirstOrDefault(s => s.Name.Equals(siteOfInterest))
.GetWebConfiguration()
.GetSection("system.webServer/security/ipSecurity")
.GetCollection();
}
}
protected override void OnInit(EventArgs arguments)
{
CheckSecurity(true);
BindRepeater();
}
public void Page_Load(object sender, EventArgs args)
{
CheckSecurity(true);
if (!IsPostBack)
{
BindRepeater();
}
}
private void BindRepeater()
{
rpt.DataSource = IpCollection
.Select(ip => ip.GetAttributeValue("ipAddress").ToString())
.OrderBy(ip => ip);
rpt.DataBind();
}
private void RemoveValue(object source, CommandEventArgs commandEventArgs)
{
var ipToRemove = (string) commandEventArgs.CommandArgument;
IpCollection
.FirstOrDefault(ip => ip.GetAttributeValue("ipAddress").Equals(ipToRemove))
.Delete();
ServerManager.CommitChanges();
BindRepeater();
}
private void SaveValue(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtValue.Text))
{
return;
}
var newIpElement = IpCollection.CreateElement();
newIpElement.SetAttributeValue("ipAddress", txtValue.Text);
newIpElement.SetAttributeValue("allowed", true);
IpCollection.Add(newIpElement);
ServerManager.CommitChanges();
BindRepeater();
txtValue.Text = string.Empty;
}
private void CheckSecurity(bool isDeveloperAllowed)
{
if (Sitecore.Context.User.IsAdministrator || (isDeveloperAllowed && this.IsDeveloper)) return;
var site = Sitecore.Context.Site;
if (site != null)
{
base.Response.Redirect(string.Format("{0}?returnUrl={1}", site.LoginPage, HttpUtility.UrlEncode(base.Request.Url.PathAndQuery)));
}
}
private bool IsDeveloper
{
get
{
return User.IsInRole(@"sitecore\developer") || User.IsInRole(@"sitecore\sitecore client developing");
}
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Manage white-listed IP's</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="Stylesheet" type="text/css" href="../default.css" />
<link rel="Stylesheet" type="text/css" href="/sitecore/shell/themes/standard/default/WebFramework.css" />
<style type="text/css">
body {
font-size: 16px !important;
background-attachment: fixed;
}
form.wf-container {
width: auto;
max-width: 860px;
}
.wf-content {
padding: 1em !important;
}
h1 {
padding: 1em 0 !important;
}
</style>
</head>
<body>
<form id="form1" runat="server" class="wf-container">
<div class="wf-content">
<h1>Manage white-listed IP's</h1>
<table cellspacing="1" cellpadding="1" border="1">
<tr>
<th>IP address</th>
<th> </th>
</tr>
<asp:Repeater runat="server" id="rpt" ItemType="System.String" OnItemCommand="RemoveValue">
<ItemTemplate>
<tr style="<%# Container.ItemIndex % 2 == 1 ? string.Empty : "background-color: #ddd" %>">
<td><%#: Item %></td>
<td style="text-align: center">
<asp:LinkButton ForeColor="Red"
runat="server"
OnCommand="RemoveValue"
CommandArgument="<%#: Item %>"
OnClientClick="return confirm('Remove this IP?')">×</asp:LinkButton>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
<tr>
<td><asp:TextBox runat="server" id="txtValue" ValidationExpression="" /></td>
<td style="text-align: center">
<asp:LinkButton ForeColor="Green" runat="server" OnClick="SaveValue">Add</asp:LinkButton>
</td>
</tr>
</table>
<asp:RegularExpressionValidator ID="RegularExpressionValidatorIp" runat="server"
ErrorMessage="Invalid IP Address!"
ValidationExpression="^(?!$)(?!.*?::.*?::)(?!.*?::.*?:$)(?!(?:[\da-fA-F]{1,4}(?::|$)){1,7}$)(?!(?:[\da-fA-F]{1,4}:){8})(?!:?(?::[\da-fA-F]{1,4}){8})(?!(?:[\da-fA-F]{1,4}:){7,}(?:\d+(?:\.|$)){4})(?!(?:[\da-fA-F]{1,4}:){6,}:(?:\d+(?:\.|$)){4})(?!(?:[\da-fA-F]{0,4}:){8,}(?:\d+(?:\.|$)){4})(?!(?:[\da-fA-F]{1,4}:){1,5}(?:\d+(?:\.|$)){4})(?:(?:::)?(?:[\da-fA-F]{1,4}(?:::?|$)){0,8})?(?:(?:(?:25[0-5]|2[0-4][0-9]|(?!00)1?[0-9][0-9]?)(?:\.|$)){4})?$"
ControlToValidate="txtValue"></asp:RegularExpressionValidator>
</div>
</form>
</body>
</html>
tags: Sitecore - C# - Back-end