]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsSecurity.java
Fix automated Kerberos config
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / CmsSecurity.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.io.IOException;
4 import java.net.Inet6Address;
5 import java.net.InetAddress;
6 import java.net.URL;
7 import java.net.UnknownHostException;
8 import java.nio.file.Files;
9 import java.nio.file.Path;
10 import java.security.PrivilegedExceptionAction;
11 import java.util.ArrayList;
12 import java.util.Iterator;
13
14 import javax.security.auth.Subject;
15 import javax.security.auth.callback.Callback;
16 import javax.security.auth.callback.CallbackHandler;
17 import javax.security.auth.callback.NameCallback;
18 import javax.security.auth.callback.UnsupportedCallbackException;
19 import javax.security.auth.kerberos.KerberosPrincipal;
20 import javax.security.auth.login.Configuration;
21 import javax.security.auth.login.LoginContext;
22 import javax.security.auth.login.LoginException;
23
24 import org.apache.commons.httpclient.auth.AuthPolicy;
25 import org.apache.commons.httpclient.auth.CredentialsProvider;
26 import org.apache.commons.httpclient.cookie.CookiePolicy;
27 import org.apache.commons.httpclient.params.DefaultHttpParams;
28 import org.apache.commons.httpclient.params.HttpMethodParams;
29 import org.apache.commons.httpclient.params.HttpParams;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.argeo.cms.CmsException;
33 import org.argeo.cms.internal.http.NodeHttp;
34 import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
35 import org.argeo.cms.internal.http.client.SpnegoCredentialProvider;
36 import org.argeo.naming.DnsBrowser;
37 import org.argeo.node.NodeConstants;
38 import org.ietf.jgss.GSSCredential;
39 import org.ietf.jgss.GSSException;
40 import org.ietf.jgss.GSSManager;
41 import org.ietf.jgss.GSSName;
42 import org.ietf.jgss.Oid;
43
44 /** Low-level kernel security */
45 public class CmsSecurity implements KernelConstants {
46 private final static Log log = LogFactory.getLog(CmsSecurity.class);
47 // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
48 public final static Oid KERBEROS_OID;
49 static {
50 try {
51 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
52 } catch (GSSException e) {
53 throw new IllegalStateException("Cannot create Kerberos OID", e);
54 }
55 }
56
57 public final static int DEPLOYED = 30;
58 public final static int STANDALONE = 20;
59 public final static int DEV = 10;
60 public final static int UNKNOWN = 0;
61
62 private String hostname;
63
64 private final int securityLevel;
65 private Subject nodeSubject;
66
67 // IPA
68 private String kerberosDomain;
69 private String service = null;
70 private GSSCredential acceptorCredentials;
71
72 private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
73
74 CmsSecurity() {
75 // Register client-side SPNEGO auth scheme
76 AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
77 HttpParams params = DefaultHttpParams.getDefaultParams();
78 ArrayList<String> schemes = new ArrayList<>();
79 schemes.add(SpnegoAuthScheme.NAME);
80 params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
81 params.setParameter(CredentialsProvider.PROVIDER, new SpnegoCredentialProvider());
82 params.setParameter(HttpMethodParams.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
83 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
84
85 if (!DeployConfig.isInitialized()) // first init
86 FirstInit.prepareInstanceArea();
87
88 securityLevel = evaluateSecurityLevel();
89 // Configure JAAS first
90 if (System.getProperty(JAAS_CONFIG_PROP) == null) {
91 String jaasConfig = securityLevel < DEPLOYED ? JAAS_CONFIG : JAAS_CONFIG_IPA;
92 URL url = getClass().getClassLoader().getResource(jaasConfig);
93 System.setProperty(JAAS_CONFIG_PROP, url.toExternalForm());
94 }
95 // explicitly load JAAS configuration
96 Configuration.getConfiguration();
97 nodeSubject = logInKernel();
98
99 // firstInit = !new File(getOsgiInstanceDir(), DIR_NODE).exists();
100
101 // this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(),
102 // "node.p12");
103 // createKeyStoreIfNeeded();
104 }
105
106 private int evaluateSecurityLevel() {
107 int res = UNKNOWN;
108 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
109 InetAddress localhost = InetAddress.getLocalHost();
110 hostname = localhost.getHostName();
111 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
112 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
113 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
114 kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
115 if (consistentIp && kerberosDomain != null && Files.exists(nodeKeyTab)) {
116 res = DEPLOYED;
117 } else {
118 res = STANDALONE;
119 kerberosDomain = null;
120 // FIXME make state more robust
121 }
122 } catch (UnknownHostException e) {
123 hostname = "localhost";
124 log.warn("Cannot determine hostname, using " + hostname + ":" + e.getMessage());
125 res = STANDALONE;
126 } catch (Exception e) {
127 log.warn("Exception when evaluating security level, setting it to DEV", e);
128 res = DEV;
129 }
130
131 if (res == UNKNOWN)
132 throw new CmsException("Undefined security level");
133 return res;
134 }
135
136 private Subject logInKernel() {
137 final Subject nodeSubject = new Subject();
138
139 CallbackHandler callbackHandler;
140 if (Files.exists(nodeKeyTab)) {
141 service = NodeHttp.DEFAULT_SERVICE;
142 // service = NodeConstants.NODE_SERVICE;
143 callbackHandler = new CallbackHandler() {
144
145 @Override
146 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
147 for (Callback callback : callbacks)
148 if (callback instanceof NameCallback)
149 ((NameCallback) callback).setName(getKerberosServicePrincipal());
150
151 }
152 };
153 try {
154 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject,
155 callbackHandler);
156 kernelLc.login();
157 } catch (LoginException e) {
158 throw new CmsException("Cannot log in kernel", e);
159 }
160 } else {
161 callbackHandler = null;
162 // try {
163 // callbackHandler = (CallbackHandler)
164 // Class.forName("com.sun.security.auth.callback.TextCallbackHandler")
165 // .newInstance();
166 // } catch (ReflectiveOperationException e) {
167 // throw new CmsException("Cannot create text callback handler", e);
168 // }
169 try {
170 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject);
171 kernelLc.login();
172 } catch (LoginException e) {
173 throw new CmsException("Cannot log in kernel", e);
174 }
175 }
176
177 if (securityLevel >= DEPLOYED) {
178 acceptorCredentials = logInAsAcceptor(nodeSubject);
179 }
180 return nodeSubject;
181 }
182
183 private String getKerberosServicePrincipal() {
184 if (hostname == null || "locahost".equals(hostname) || kerberosDomain == null || service == null)
185 throw new IllegalStateException("Cannot determine kerberos principal");
186 return service + "/" + hostname + "@" + kerberosDomain;
187 }
188
189 private GSSCredential logInAsAcceptor(Subject nodeSubject) {
190 // GSS
191 Iterator<KerberosPrincipal> krb5It = nodeSubject.getPrincipals(KerberosPrincipal.class).iterator();
192 if (!krb5It.hasNext())
193 return null;
194 KerberosPrincipal krb5Principal = null;
195 while (krb5It.hasNext()) {
196 KerberosPrincipal principal = krb5It.next();
197 if (service == null && krb5Principal == null)// first as default
198 krb5Principal = principal;
199 if (service != null && principal.getName().equals(getKerberosServicePrincipal()))
200 krb5Principal = principal;
201 }
202
203 if (krb5Principal == null)
204 return null;
205
206 GSSManager manager = GSSManager.getInstance();
207 try {
208 GSSName gssName = manager.createName(krb5Principal.getName(), null);
209 GSSCredential serverCredentials = Subject.doAs(nodeSubject, new PrivilegedExceptionAction<GSSCredential>() {
210
211 @Override
212 public GSSCredential run() throws GSSException {
213 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
214 GSSCredential.ACCEPT_ONLY);
215
216 }
217
218 });
219 if (log.isDebugEnabled())
220 log.debug("GSS acceptor configured for " + krb5Principal);
221 return serverCredentials;
222 } catch (Exception gsse) {
223 throw new CmsException("Cannot create acceptor credentials for " + krb5Principal, gsse);
224 }
225 }
226 //
227 // private Subject logInHardenedKernel() {
228 // final Subject kernelSubject = new Subject();
229 // createKeyStoreIfNeeded();
230 //
231 // CallbackHandler cbHandler = new CallbackHandler() {
232 //
233 // @Override
234 // public void handle(Callback[] callbacks) throws IOException,
235 // UnsupportedCallbackException {
236 // // alias
237 //// ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL);
238 // // store pwd
239 // ((PasswordCallback) callbacks[2]).setPassword("changeit".toCharArray());
240 // // key pwd
241 // ((PasswordCallback) callbacks[3]).setPassword("changeit".toCharArray());
242 // }
243 // };
244 // try {
245 // LoginContext kernelLc = new
246 // LoginContext(KernelConstants.LOGIN_CONTEXT_HARDENED_KERNEL,
247 // kernelSubject,
248 // cbHandler);
249 // kernelLc.login();
250 // } catch (LoginException e) {
251 // throw new CmsException("Cannot log in kernel", e);
252 // }
253 // return kernelSubject;
254 // }
255
256 void destroy() {
257 // Logout kernel
258 try {
259 LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject);
260 kernelLc.logout();
261 } catch (LoginException e) {
262 throw new CmsException("Cannot log out kernel", e);
263 }
264
265 // Security.removeProvider(SECURITY_PROVIDER);
266 }
267
268 public synchronized int getSecurityLevel() {
269 return securityLevel;
270 }
271
272 public String getKerberosDomain() {
273 return kerberosDomain;
274 }
275
276 public Subject getNodeSubject() {
277 return nodeSubject;
278 }
279
280 public GSSCredential getServerCredentials() {
281 return acceptorCredentials;
282 }
283
284 // public void setSecurityLevel(int newValue) {
285 // if (newValue != STANDALONE || newValue != DEV)
286 // throw new CmsException("Invalid value for security level " + newValue);
287 // if (newValue >= securityLevel)
288 // throw new CmsException(
289 // "Impossible to increase security level (from " + securityLevel + " to " +
290 // newValue + ")");
291 // securityLevel = newValue;
292 // }
293
294 // private void createKeyStoreIfNeeded() {
295 // // for (Provider provider : Security.getProviders())
296 // // System.out.println(provider.getName());
297 //
298 // char[] ksPwd = "changeit".toCharArray();
299 // char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
300 // if (!keyStoreFile.exists()) {
301 // try {
302 // keyStoreFile.getParentFile().mkdirs();
303 // KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd);
304 // // PkiUtils.generateSelfSignedCertificate(keyStore, new
305 // // X500Principal(AuthConstants.ROLE_KERNEL), 1024,
306 // // keyPwd);
307 // PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore);
308 // if (log.isDebugEnabled())
309 // log.debug("Created keystore " + keyStoreFile);
310 // } catch (Exception e) {
311 // if (keyStoreFile.length() == 0)
312 // keyStoreFile.delete();
313 // log.error("Cannot create keystore " + keyStoreFile, e);
314 // }
315 // }
316 // }
317
318 // private final static String SECURITY_PROVIDER = "BC";// Bouncy Castle
319 // private final static Log log;
320 // static {
321 // log = LogFactory.getLog(NodeSecurity.class);
322 // // Make Bouncy Castle the default provider
323 // Provider provider = new BouncyCastleProvider();
324 // int position = Security.insertProviderAt(provider, 1);
325 // if (position == -1)
326 // log.error("Provider " + provider.getName()
327 // + " already installed and could not be set as default");
328 // Provider defaultProvider = Security.getProviders()[0];
329 // if (!defaultProvider.getName().equals(SECURITY_PROVIDER))
330 // log.error("Provider name is " + defaultProvider.getName()
331 // + " but it should be " + SECURITY_PROVIDER);
332 // }
333 }