In order to get the Weblogic initial context to query the task database i am doing the following:
Properties h = new Properties();
h.put(Context.SECURITY_PRINCIPAL, "weblogic");
h.put(Context.PROVIDER_URL, "t3://localhost:17101");
h.put(Context.SECURITY_CREDENTIALS, "weblogic");
h.put(Context.SECURITY_AUTHENTICATION, "simple");
WLInitialContextFactory test = new WLInitialContextFactory();
test.getInitialContext(h);
Context ctx = null;
ctx = getInitialContext();
WorklistContext wliContext = WorklistContextFactory.getRemoteWorklistContext(ctx, "MyTaskApplication");
I then get the TaskQuery interface with the following code:
WorklistTaskQuery taskQuery = wliContext.getInterfaceForTaskQuery();
and to get the tasks i do:
taskQuery.getTasks(query);
where query is com.bea.wli.worklist.api.TaskQuery object.
Please note that this code is running inside the domain running the tasks.
Unfortunally i am getting the following error when i call the getTasks methods:
java.lang.SecurityException: [WLI-Worklist:493103]Access denied to resource /taskplans
/Manual:1.0. Applicable policy: Query Caller: principals=[] Method: com.bea.wli.worklist.security.WorklistSecurityManager.assertTaskAccessAllowed
It seems Weblogic is ignoring the user set on the new initial context and trying to use the one coming from the browser. It so happens that i might need to do query searchs in background workers that don't have a browser session(obviously).
Can anyone help with this?
I've found a solution for this, though it's convoluted and ugly as hell.
Since i'm making these calls through an EJB i can authenticate the call by grabbing the EJB implementation from an authenticated context like so:
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.SECURITY_PRINCIPAL,"user");
env.put(Context.PROVIDER_URL,"t3://localhost:7001");
env.put(Context.SECURITY_CREDENTIALS,"password");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
getSessionInterface(interfaceClass, new InitialContext(env));
Your best bet for this is to avoid the above example and this API all together. Just use the regular MBean Implementation which allows authentication.
Update this solution doesn't seem to be viable, it will screw up the transaction management. Will report back, but it seems if you need to create tasks outside of an authenticated context you will need to go the MBean way
Related
Please Note:- I just want to validate whether the following can be achieved using the JAAS/GSSAPI. I am not able to find any pointers.
Let me first clear the constraints on my application:
We can't have a static krb.conf file. It is dynamic and constantly changing. Hence, caching LoginContext objects in memory is not possible as the Subject's credentials are invalidated once the krb.conf file is modified.
We want to manage large set of realms of which we don't have any prior information, hence static krb.conf file is not possible.
"sun.security.krb5.internal.tools.Kinit" is proprietary and always get a warning it might get removed in future releases. So we can't use this to generate the cache because of the risk of getting a runtime issues. Note: Cached TGTs don't expire even if the krb.conf file is changed natively. But the problem here is, we will have to make our code of generating and maintaining the krb.conf file much complex. We want to avoid this
We are keeping the option of running the kinit tool using Runtime#exec(...) as the last resort to generate the TGT cache files (which will reduce the dependency on sun.security.krb5.internal.tools.Kinit package) but will make our krb.conf generation and maintenance logic more complex.
I am in search of figuring out a more easy solution.
Our current implementation is very straightforward: We change our krb.conf file to suit the current realm and domain information and then delete the file. Everything being very dynamic, it is very efficient but the problem is, we are not able to cache the TGTs and service tickets which increases the load on KDC server and results in performance hit!
With the thorough introduction, let us move to the PoC that I am trying out. Please provide pointers or on:
Whether I am moving in the right direction or not?
Are the things that I am envisioning even possible or not?
If anyone of you have come across such a scenario, what strategy have you employed?
We are connecting to Active Directory DCs to form a LdapContext/ DirContext to fetch the AD object Data.
When there is not cached data available, proceed normally to create the LoginContext as usual:
System.setProperty("java.security.krb5.conf", "C:\\Windows\\krb5.ini");
System.setProperty("sun.security.jgss.debug","true");
LoginContext lc = null;
try {
lc = new LoginContext(LdapCtxGSSAPIEx.class.getName(),
new LoginCallbackHandler(username,password));
// Attempt authentication
// You might want to do this in a "for" loop to give
// user more than one chance to enter correct username/password
lc.login();
} catch (LoginException le) {
System.err.println("Authentication attempt failed" + le);
System.exit(-1);
}
Solution #1:
Once the valid LoginContext is created, use GSSAPI to get the service ticket for ldap. I know of the other way (which actually is our current implementation, and shown in Solution#2) to form the LdapContext in the PrivilegedAction#run(). But that is not helping in caching. Hence, trying a PoC on storing the Service Ticket instead of the TGT. Getting the ldap service ticket is as follows:
// servicePrincipalName = ldap/ad01.example.lab#EXAMPLE.LAB
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName( servicePrincipalName,
GSSName.NT_HOSTBASED_SERVICE);
final GSSContext context = manager.createContext( serverName, krb5OID, null,
GSSContext.DEFAULT_LIFETIME);
// The GSS context initiation has to be performed as a privileged action.
byte[] serviceTicket = Subject.doAs( subject, new PrivilegedAction<byte[]>() {
public byte[] run() {
try {
byte[] token = new byte[0];
// This is a one pass context initialisation.
context.requestMutualAuth( false);
context.requestCredDeleg( false);
return context.initSecContext( token, 0, token.length);
}
catch ( GSSException e) {
e.printStackTrace();
return null;
}
}
});
Questions:
How can I use this service ticket to form the LdapContext ?
Can I store this ticket in a file (encoded) and then later use the ticket in the same way to form the LdapContext ?
Solution #2:
Creating the LdapContext as seen in most of the tutorials and also in our current implementation:
DirContext ctx = Subject.doAs(lc.getSubject(), new JndiAction<DirContext>(args,providerURL ));
class JndiAction<DirContext> implements java.security.PrivilegedAction<DirContext> {
.......
public DirContext run() {
return performJndiOperation(args, this.providerURL);
}
public DirContext performJndiOperation(String[] args, String providerURL){
......
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// Must use fully qualified hostname
env.put(Context.PROVIDER_URL, providerURL);
// Request the use of the "GSSAPI" SASL mechanism
// Authenticate by using already established Kerberos credentials
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
// Request mutual authentication
env.put("javax.security.sasl.server.authentication", "true");
DirContext ctx = new InitialDirContext(env);
return ctx;
......
}
.......
}
When this line: DirContext ctx = new InitialDirContext(env); in the above code is executed: the Subject is populated with a new PrivateCredential with the service principal : ldap/ad01.example.lab#EXAMPLE.LAB
Question: Can I store this service ticket for further reference to create the LdapContext instead of again performing all the steps of authentication again and again?
What are delegation credentials? Would they help in solving my issue?
UPDATE:
Why store the service ticket and not the TGT in cache? ---> To avoid kinit explicitly. Storing the service ticket will fit in with our current solution easily. Also, this is going to be a temporary solution as we are going to ask our customers the krb.conf file as per their needs and get off the responsibility of creating a krb.conf file. But for now, we have to do this!
Please Help!
I don't want to use jdni.properties file, so to add new properties to my JNDI settings, I wrote following:
Hashtable<String, Object> jndi_env = new Hashtable<String, Object>();
jndi_env.put(InitialContext.INITIAL_CONTEXT_FACTORY, "org.apache.activemq.jndi.ActiveMQInitialContextFactory");
jndi_env.put("connectionFactory.ConnectionFactory","vm://0");
jndi_env.put("topic.example","example");
My Problem is, when I call this class:
initialContext = new InitialContext(jndi_env);
Since I pass a name parameter in the last line a URL context factory is looked up.
This makes my code looking for a tcp://localhost:61616 connection which I actually don't want.
I see that there are
QueueConnectionFactory: org.apache.activemq.ActiveMQConnectionFactory
example: org.apache.activemq.command.ActiveMQTopic
XAConnectionFactory: org.apache.activemq.ActiveMQXAConnectionFactory
which I don't want, or at least not the type they are.
If I check without passing an argument using my jndi.properties file where I don't get the issue of establishing a tcp connection, then I find just:
ConnectionFactory: org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory
queue: org.apache.activemq.artemis.jndi.ReadOnlyContext
queue/exampleQueue: org.apache.activemq.artemis.jms.client.ActiveMQQueue
dynamicTopics: org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory$2
dynamicQueues: org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory$1
So how can I change the object types of my added jndi_env.put("topic.example","example"); so it will be like this (but of course for Topics)
queue: org.apache.activemq.artemis.jndi.ReadOnlyContext
queue/exampleQueue: org.apache.activemq.artemis.jms.client.ActiveMQQueue
When you create your InitialContext you're passing in the wrong factory. Currently you're passing in org.apache.activemq.jndi.ActiveMQInitialContextFactory. This is the factory for ActiveMQ 5.x, not Artemis. You need to pass in org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory instead, e.g.:
Hashtable<String, Object> jndi_env = new Hashtable<String, Object>();
jndi_env.put(InitialContext.INITIAL_CONTEXT_FACTORY, "org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory");
jndi_env.put("connectionFactory.ConnectionFactory","vm://0");
jndi_env.put("topic.example","example");
I'm using InitialLdapContext in order to search a Ldap directory .
Though it behaves strange - if i run the search query 1-5 times in a row, its working ok, returns an answer immediately , though if i run it more than that it suddenly doesn't return , and hangs until timeout .
But if i try to do the same thing through an Ldap Client for exmaple JXplorer , it never give me a timeout .. which means there is something wrong with my code.
In addition, I'm not sure it even regards to the timeout I set to. I tried both 5sec timeout and 15sec timeout and occasionally it will wait for much more than that.
Here is the code sample:
Hashtable<String,Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "my url");
env.put(Context.SECURITY_AUTHENTICATION, "none");
env.put("com.sun.jndi.ldap.connect.timeout", "5000");
InitialLdapContext context = new InitialLdapContext(env,null);
NamingEnumeration<SearchResult> me = context.search("cn=*,cn=nfs,ou=policy,ou=Service,dc=doo,dc=myorg,dc=com",
FILTER_WITH_DEPTH.replace("replaceMe", String.valueOf(4)), getSimpleSearchControls());
me.hasMore();
me.close();
What am I doing wrong ?
We are working on a custom password reset tool which is currently able to reset the passwords for users (using the admin DN), but I need to also remove/modify some Operational Attributes in order to completely handle the business use cases. I connect to the LDAP server using:
private void connect() throws NamingException {
Properties props = new Properties();
props.put(INITIAL_CONTEXT_FACTORY, LDAP_CTX_FACTORY);
props.put(PROVIDER_URL, format("ldap://%s:%d/", config.ldapHost(), config.ldapPort()));
props.put(SECURITY_CREDENTIALS, config.ldapBindPassword());
props.put(SECURITY_PRINCIPAL, config.ldapBindUser());
props.put(SECURITY_AUTHENTICATION, "simple");
props.put(REFERRAL, "follow");
props.put(BATCHSIZE, "1000");
connection = new InitialLdapContext(props, null);
connection.setRequestControls(LDAPControls.controls());
LOG.debug("Successfully completed bind to LDAP server '{}'", config.ldapHost());
connected = true;
}
And I need to modify some operational attributes to do things like unlock accounts/update modified time/etc...
List<BasicAttribute> attrs = new ArrayList<>();
List<ModificationItem> mods = new ArrayList<>();
// Set password hash
attrs.add(new BasicAttribute("userPassword", "{SSHA}" + hashPassword(salt, password)));
mods.add(new ModificationItem(REPLACE_ATTRIBUTE, attrs.get(0)));
// Set last modified timestamp
attrs.add(new BasicAttribute("modifyTimestamp", date.withZone(UTC).format(now())));
mods.add(new ModificationItem(REPLACE_ATTRIBUTE, attrs.get(1)));
// Set password changed time
attrs.add(new BasicAttribute("pwdChangeTime", date.withZone(UTC).format(now())));
mods.add(new ModificationItem(REPLACE_ATTRIBUTE, attrs.get(2)));
// Remove password lock
attrs.add(new BasicAttribute("pwdAccountLockedTime"));
mods.add(new ModificationItem(REMOVE_ATTRIBUTE, attrs.get(3)));
// Clear password failure time
attrs.add(new BasicAttribute("pwdFailureTime"));
mods.add(new ModificationItem(REMOVE_ATTRIBUTE, attrs.get(4)));
this.reconnect();
ModificationItem[] modItems = new ModificationItem[mods.size()];
mods.toArray(modItems);
connection.modifyAttributes(getDN(email), modItems);
LOG.debug("Completed update of user password for '{}'", email);
return true;
But when I run this, I get:
LDAP: error code 21 - modifyTimestamp: value #0 invalid per syntax
Could anyone help me to figure out why?
How do I modify Operational Attributes in OpenLDAP from Java/JNDI?
You don't. The server does. That's what 'operational attribute' means.
I need to also remove/modify some Operational Attributes in order to completely handle the business use cases
Bad luck.
You should be using the 'ppolicy' overlay and the associated extended password-modify operations, rather than rolling all this yourself. It does everything you need, and if it doesn't you need to adjust your needs ;-)
NB You should not hash the password yourself. OpenLDAP will do that for you when configured correctly.
Tomcat offers a build in "Virtual Hosting" Support: An Engine/Web-Application can be configured to be responsible for a list of Domains. These Domains have to be put into the server.xml/context.xml files with a special xml directive.
=> Is there any possibility to change the Tomcat Configuration (in general) and especially the "Virtual Hosts" of a Web-Application/Engine programmatically?
For example if a new user signs up, I have to add his domain to the list of "accepted virtual hosts/domains". The only way I currently think of is changing the xml files via a script and then restart Tomcat.
Is there any way to add them add runtime via some Java-Methods programmatically?
Thank you very much!
Jan
Tomcat provides APIs to create new virtual host. To get access to the wrapper object needed for this, you need to implement a ContainerServlet. You can create virtual host like this,
Context context = (Context) wrapper.getParent();
Host currentHost = (Host) context.getParent();
Engine engine = (Engine) currentHost.getParent();
StandardHost host = new StandardHost();
host.setAppBase(appBase);
host.setName(domainName);
engine.addChild(host);
You need to make sure appBase directory exist and you have to find ways to persist the new host to the server.xml or you lose the host on restart.
However, this approach rarely works. If your users run their own apps, you really want run separate instances of Tomcat so you can sandbox the apps better. e.g. One app running out of memory doesn't kill all other apps.
If you provide the app, you can just use one host (defaultHost). You can get the domain name from Host header and do whatever domain-specific stuff in your code.
You shouldn't change the server environment programmatically and there are no reliable and standard ways to do this. Best is to do and keep it all on the webapp side. To start, a Filter is perfectly suitable for this. Store the names somewhere in a database table or a properties file which you cache in the application scope. Check the HttpServletRequest#getRequestURI() (or the getServerName() if it is a subdomain instead of pathinfo) and do the forwarding task accordingly.
Hope this helps.
Use JMX
ArrayList serverList = MBeanServerFactory.findMBeanServer(null);
MBeanServer server = (MBeanServer) serverList.get(0);
Object[] params = { "org.apache.catalina.core.StandardHost", hostName };
String[] signature = { "java.lang.String", "java.lang.String" };
server.invoke(new ObjectName("Catalina:type=Engine"), "addChild", params, signature);
If needed, retrieve the host object and work with it:
ObjectName host = new ObjectName("Catalina:type=Host,host=" + hostName);
server.setAttribute(host, new Attribute("autoDeploy", false));
server.invoke(host, "start", null, null);
I would suggest you set your application to be the default virtual host in server.xml so your single virtual host can respond to requests addressed to any host name. Tomcat ships with the localhost application set as the default virtual host. So you can see how to do this by simply inspecting the server.xml file of a vanilla tomcat installation. You can programatically determine the host name the user sent the request to using the ServletRequest.getServerName() method.
Tomcat used to ship with a web application called "host-manager". Note: this is different than the "manager" web application that still comes with Tomcat. Host manager allowed for changing configuration or adding new virtual hosts on the fly without restarting the server. You could interact with the host-manager over HTTP (programmatically if desired). However, it had the unfortunate flaw of not committing its changes to server.xml so they were all lost on a web server restart. For whatever reason, starting with version 6, Tomcat no longer ships with the host-manager application. So it doesn't appear to be supported anymore.
To sum up ZZ Coder answer which guided me a lot:
You have to create a servlet that implements ContainerServlet and override setWrapper method to get the org.apache.catalina.Wrapper object.
For doing that you have to have privileged="true" in your context.xml Context tag or it will throw an exception. Then you can use the Wrapper object and:
StandardContext context = (StandardContext) wrapper.getParent();
StandardHost currentHost = (StandardHost) context.getParent();
StandardEngine engine = (StandardEngine) currentHost.getParent();
StandardHost host = new StandardHost();
host.setAppBase(currentHost.getAppBase()); //in my case I created another instance of the same application
host.setDomain(currentHost.getDomain());
host.setAutoDeploy(false); // not restarting app whenever changes happen
host.setName("domain.com");
host.setThrowOnFailure(true);// tell it to throw an exception here if it fails to create the host
host.setDeployOnStartup(true);
host.setStartChildren(true);
host.setParent(engine);
// you can add multiple aliases
host.addAlias(alias);
StandardContext ctx = new StandardContext();
ctx.setDocBase(context.getDocBase()); //again I reused my same application setting
ctx.setPath("");
if(currentHost.getWorkDir() != null)
{//create a working directory based on your new host's name
ctx.setWorkDir(currentHost.getWorkDir().replace(currentHost.getName(), host.getName()));
}
ctx.setName(host.getDomain());
//some extra config that you can use
ctx.setUseHttpOnly(false);
ctx.setReloadable(false);
ctx.setXmlValidation(false);
ctx.setXmlNamespaceAware(false);
ctx.setCrossContext(false);
ctx.setParent(host);
// you have to have this or it will not work!!
ctx.addLifecycleListener(new ContextConfig());
//you can also create resources and add it to the context like so:
final ContextResource res = new ContextResource();
res.setName("name");
res.setAuth("Container");
res.setType("javax.sql.DataSource");
ctx.getNamingResources().addResource(res);
host.addChild(ctx);
engine.addChild(host);
You can add properties to your resource by calling res.setProperty("name", "value")
Some properties that you can use are:
initialSize,maxTotal,maxIdle,maxWaitMillis,removeAbandonedOnBorrow,removeAbandonedTimeout,validationQuery,timeBetweenEvictionRunsMillis,driverClassName,url,username,password.
Another exciting thing to is to get the host from the tomcat engine by calling engine.findChild(domain) and use stop(), start(), getStateName() and have your own Tomcat Admin panel!